From d37eea9a47cc2dc6f2ada4406367dbbdbf7dcef8 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 6 Dec 2016 09:17:07 +0100 Subject: [PATCH] write action progress: paint only in EDT, don't reschedule events after finishing instead, consume input events when they're posted --- .../src/com/intellij/ide/IdeEventQueue.java | 20 +++++ .../TestWriteActionUnderProgress.java | 4 +- .../progress/util/PotemkinProgress.java | 83 ++++++++++--------- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index bc5dc9c12c64..9610ccbdd8a5 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -122,6 +122,8 @@ public class IdeEventQueue extends EventQueue { private boolean myDispatchingFocusEvent; private boolean myWinMetaPressed; private int myInputMethodLock; + private final com.intellij.util.EventDispatcher + myPostEventListeners = com.intellij.util.EventDispatcher.create(PostEventHook.class); private static class IdeEventQueueHolder { private static final IdeEventQueue INSTANCE = new IdeEventQueue(); @@ -1121,6 +1123,10 @@ public class IdeEventQueue extends EventQueue { private final FrequentEventDetector myFrequentEventDetector = new FrequentEventDetector(1009, 100); @Override public void postEvent(@NotNull AWTEvent event) { + for (PostEventHook listener : myPostEventListeners.getListeners()) { + if (listener.consumePostedEvent(event)) return; + } + myFrequentEventDetector.eventHappened(event); if (isKeyboardEvent(event)) { myKeyboardEventsPosted.incrementAndGet(); @@ -1163,4 +1169,18 @@ public class IdeEventQueue extends EventQueue { public enum BlockMode { COMPLETE, ACTIONS } + + /** + * An absolutely guru API, please avoid using it at all cost. + */ + public interface PostEventHook extends EventListener { + /** + * @return true if event is handled by the listener and should't be added to event queue at all + */ + boolean consumePostedEvent(@NotNull AWTEvent event); + } + + public void addPostEventListener(@NotNull PostEventHook listener, @NotNull Disposable parentDisposable) { + myPostEventListeners.addListener(listener, parentDisposable); + } } diff --git a/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java b/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java index e80f6ed5d73b..fd8348f17f62 100644 --- a/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java +++ b/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java @@ -49,8 +49,8 @@ public class TestWriteActionUnderProgress extends DumbAwareAction { private static void runIndeterminateProgress(ProgressIndicator indicator) { indicator.setIndeterminate(true); indicator.setText("Indeterminate"); - for (int i = 0; i < 200; i++) { - TimeoutUtil.sleep(10); + for (int i = 0; i < 1000; i++) { + TimeoutUtil.sleep(5); indicator.checkCanceled(); } } 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 index ca291ef2c363..863e4770d52f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/PotemkinProgress.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/PotemkinProgress.java @@ -15,22 +15,24 @@ */ package com.intellij.openapi.progress.util; +import com.intellij.concurrency.JobScheduler; import com.intellij.ide.IdeEventQueue; -import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.util.io.storage.HeavyProcessLatch; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import sun.awt.SunToolkit; import javax.swing.*; import java.awt.*; import java.awt.event.InputEvent; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; /** * A progress indicator for processes running in EDT. Paints itself in checkCanceled calls. @@ -38,15 +40,32 @@ import java.util.Objects; * @author peter */ public class PotemkinProgress extends ProgressWindow { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.util.PotemkinProgress"); private long myLastUiUpdate = System.currentTimeMillis(); - private final List myDelayedEvents = new ArrayList<>(); - private IdeEventQueue myEventQueue = IdeEventQueue.getInstance(); + private final Queue myEventQueue = new ConcurrentLinkedQueue<>(); public PotemkinProgress(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText) { super(cancelText != null,false, project, parentComponent, cancelText); setTitle(title); installCheckCanceledPaintingHook(); + startStealingInputEvents(); + } + + private void startStealingInputEvents() { + checkNativeEventsRegularly(); + + IdeEventQueue.getInstance().addPostEventListener(event -> { + if (event instanceof InputEvent) { + myEventQueue.offer((InputEvent)event); + return true; + } + return false; + }, this); + } + + private void checkNativeEventsRegularly() { + ScheduledFuture future = JobScheduler.getScheduler().scheduleWithFixedDelay( + () -> SunToolkit.flushPendingEvents(), 3, 3, TimeUnit.MILLISECONDS); + Disposer.register(this, () -> future.cancel(false)); } @NotNull @@ -63,48 +82,42 @@ public class PotemkinProgress extends ProgressWindow { addStateDelegate(new AbstractProgressIndicatorExBase() { @Override public boolean isCanceled() { - dispatchAwtEventsWithoutModelAccess(); - updateUI(); + if (ApplicationManager.getApplication().isDispatchThread()) { + dispatchAwtEventsWithoutModelAccess(); + updateUI(); + } return super.isCanceled(); } }); } private void dispatchAwtEventsWithoutModelAccess() { - while (myEventQueue.peekEvent() != null) { - try { - handleEvent(myEventQueue.getNextEvent()); - } - catch (InterruptedException e) { - LOG.error(e); - return; - } + while (true) { + InputEvent event = myEventQueue.poll(); + if (event == null) return; + + dispatchInputEvent(event); } } - private void handleEvent(AWTEvent e) { - if (e instanceof InputEvent) { - dispatchInputEvent(e); - } else { - myDelayedEvents.add(e); - } - } - - private void dispatchInputEvent(AWTEvent e) { + private void dispatchInputEvent(InputEvent e) { if (isCancellationEvent(e)) { cancel(); return; } Object source = e.getSource(); - if (source instanceof Component && getDialog().getPanel().isAncestorOf((Component)source)) { + if (source instanceof Component && isInDialogWindow((Component)source)) { ((Component)source).dispatchEvent(e); } } - private void updateUI() { - if (!ApplicationManager.getApplication().isDispatchThread()) return; + private boolean isInDialogWindow(Component source) { + Window dialogWindow = SwingUtilities.windowForComponent(getDialog().getPanel()); + return dialogWindow instanceof JDialog && SwingUtilities.isDescendingFrom(source, dialogWindow); + } + private void updateUI() { JRootPane rootPane = getDialog().getPanel().getRootPane(); if (rootPane == null) { rootPane = considerShowingDialog(); @@ -136,18 +149,6 @@ public class PotemkinProgress extends ProgressWindow { public void progressFinished() { getDialog().hideImmediately(); - scheduleDelayedEventDelivery(); - } - - private void scheduleDelayedEventDelivery() { - Disposable disposable = Disposer.newDisposable(); - myEventQueue.addDispatcher(e -> { - Disposer.dispose(disposable); - for (AWTEvent event : myDelayedEvents) { - myEventQueue.dispatchEvent(event); - } - return false; - }, disposable); } /**