/* * 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.ide.IdeEventQueue; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.util.concurrency.Semaphore; 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.Objects; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * A progress indicator for write actions. Paints itself explicitly, without resorting to normal Swing's delayed repaint API. * Doesn't dispatch Swing events, except for handling manually those that can cancel it or affect the visual presentation. * * @author peter */ public class PotemkinProgress extends ProgressWindow implements PingProgress { private long myLastUiUpdate = System.currentTimeMillis(); private final LinkedBlockingQueue<InputEvent> myEventQueue = new LinkedBlockingQueue<>(); public PotemkinProgress(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText) { super(cancelText != null,false, project, parentComponent, cancelText); setTitle(title); ApplicationManager.getApplication().assertIsDispatchThread(); startStealingInputEvents(); } private void startStealingInputEvents() { IdeEventQueue.getInstance().addPostEventListener(event -> { if (event instanceof InputEvent) { myEventQueue.offer((InputEvent)event); return true; } return false; }, this); } @NotNull @Override protected ProgressDialog getDialog() { return Objects.requireNonNull(super.getDialog()); } public void interact() { if (ApplicationManager.getApplication().isDispatchThread()) { long now = System.currentTimeMillis(); if (shouldDispatchAwtEvents(now)) { dispatchAwtEventsWithoutModelAccess(0); } updateUI(now); } } private void dispatchAwtEventsWithoutModelAccess(int timeoutMs) { SunToolkit.flushPendingEvents(); try { while (true) { InputEvent event = myEventQueue.poll(timeoutMs, TimeUnit.MILLISECONDS); if (event == null) return; dispatchInputEvent(event); } } catch (InterruptedException e) { throw new RuntimeException(e); } } private long myLastShouldDispatchCheck = 0; private boolean shouldDispatchAwtEvents(long now) { if (now == myLastShouldDispatchCheck) return false; myLastShouldDispatchCheck = now; return getDialog().getPanel().isShowing(); } private void dispatchInputEvent(InputEvent e) { if (isCancellationEvent(e)) { cancel(); return; } Object source = e.getSource(); if (source instanceof Component && isInDialogWindow((Component)source)) { ((Component)source).dispatchEvent(e); } } private boolean isInDialogWindow(Component source) { Window dialogWindow = SwingUtilities.windowForComponent(getDialog().getPanel()); return dialogWindow instanceof JDialog && SwingUtilities.isDescendingFrom(source, dialogWindow); } private void updateUI(long now) { JRootPane rootPane = getDialog().getPanel().getRootPane(); if (rootPane == null) { rootPane = considerShowingDialog(now); } if (rootPane != null && timeToPaint(now)) { paintProgress(); } } @Nullable protected JRootPane considerShowingDialog(long now) { if (isReadyShowing(now)) { getDialog().myRepaintRunnable.run(); showDialog(); return getDialog().getPanel().getRootPane(); } return null; } protected boolean isReadyShowing(long now) { return now - myLastUiUpdate > myDelayInMillis; } private boolean timeToPaint(long now) { if (now - myLastUiUpdate <= ProgressDialog.UPDATE_INTERVAL) { return false; } myLastUiUpdate = now; return true; } private void progressFinished() { getDialog().hideImmediately(); } /** * Repaint just the dialog panel. We must not call custom paint methods during write action, * because they might access the model which might be inconsistent at that moment. */ private void paintProgress() { getDialog().myRepaintRunnable.run(); JPanel dialogPanel = getDialog().getPanel(); dialogPanel.validate(); dialogPanel.paintImmediately(dialogPanel.getBounds()); } /** Executes the action in EDT, paints itself inside checkCanceled calls. */ public void runInSwingThread(@NotNull Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); try { ProgressManager.getInstance().runProcess(action, this); } catch (ProcessCanceledException ignore) { } finally { progressFinished(); } } /** Executes the action in a background thread, block Swing thread, handles selected input events and paints itself periodically. */ public void runInBackground(@NotNull Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); enterModality(); try { ensureBackgroundThreadStarted(action); while (isRunning()) { dispatchAwtEventsWithoutModelAccess(10); updateUI(System.currentTimeMillis()); } } finally { exitModality(); progressFinished(); } } private void ensureBackgroundThreadStarted(@NotNull Runnable action) { Semaphore started = new Semaphore(); started.down(); ApplicationManager.getApplication().executeOnPooledThread(() -> ProgressManager.getInstance().runProcess(() -> { started.up(); action.run(); }, this)); started.waitFor(); } }