allow to exit w/o confirmation for processes which run w/o visible progresses (i...
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / progress / impl / ProgressManagerImpl.java
blob286d21c7aa58aec4cede1f108423c0693c4ba2b2
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.progress.impl;
18 import com.intellij.openapi.application.Application;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.application.ex.ApplicationEx;
22 import com.intellij.openapi.components.ServiceManager;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.progress.*;
25 import com.intellij.openapi.progress.util.ProgressWindow;
26 import com.intellij.openapi.progress.util.SmoothProgressAdapter;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.Comparing;
29 import com.intellij.openapi.util.Disposer;
30 import com.intellij.openapi.wm.WindowManager;
31 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
32 import com.intellij.psi.PsiLock;
33 import com.intellij.ui.SystemNotifications;
34 import org.jetbrains.annotations.*;
36 import javax.swing.*;
37 import java.awt.*;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.concurrent.atomic.AtomicInteger;
42 public class ProgressManagerImpl extends ProgressManager {
43 @NonNls private static final String PROCESS_CANCELED_EXCEPTION = "idea.ProcessCanceledException";
45 private static final ThreadLocal<ProgressIndicator> myThreadIndicator = new ThreadLocal<ProgressIndicator>();
46 private final AtomicInteger myCurrentProgressCount = new AtomicInteger(0);
47 private final AtomicInteger myCurrentUnsafeProgressCount = new AtomicInteger(0);
48 private final AtomicInteger myCurrentModalProgressCount = new AtomicInteger(0);
50 private static volatile int ourLockedCheckCounter = 0;
51 private final List<ProgressFunComponentProvider> myFunComponentProviders = new ArrayList<ProgressFunComponentProvider>();
52 @NonNls private static final String NAME = "Progress Cancel Checker";
54 public ProgressManagerImpl(Application application) {
55 if (!application.isUnitTestMode() && !Comparing.equal(System.getProperty(PROCESS_CANCELED_EXCEPTION), "disabled")) {
56 new Thread(NAME) {
57 public void run() {
58 while (true) {
59 try {
60 sleep(10);
62 catch (InterruptedException ignored) {
64 ourNeedToCheckCancel = true;
67 }.start();
71 @Override
72 protected void doCheckCanceled() throws ProcessCanceledException {
73 final ProgressIndicator progress = getProgressIndicator();
74 if (progress != null) {
75 try {
76 progress.checkCanceled();
78 catch (ProcessCanceledException e) {
79 if (Thread.holdsLock(PsiLock.LOCK)) {
80 ourLockedCheckCounter++;
81 if (ourLockedCheckCounter > 10) {
82 ourLockedCheckCounter = 0;
83 ourNeedToCheckCancel = true;
86 else {
87 ourLockedCheckCounter = 0;
88 progress.checkCanceled();
94 public static void canceled() {
95 ourNeedToCheckCancel = true;
98 private static class NonCancelableIndicator extends EmptyProgressIndicator implements NonCancelableSection {
99 private final ProgressIndicator myOld;
101 private NonCancelableIndicator(ProgressIndicator old) {
102 myOld = old;
105 public void done() {
106 ProgressIndicator currentIndicator = myThreadIndicator.get();
107 if (currentIndicator != this) {
108 throw new AssertionError("Trying do .done() NonCancelableSection, which is already done");
111 myThreadIndicator.set(myOld);
114 @Override
115 public void checkCanceled() {
119 public NonCancelableSection startNonCancelableSection() {
120 NonCancelableIndicator nonCancelor = new NonCancelableIndicator(myThreadIndicator.get());
121 myThreadIndicator.set(nonCancelor);
122 return nonCancelor;
125 public void executeNonCancelableSection(Runnable r) {
126 NonCancelableSection nonCancelor = startNonCancelableSection();
127 try {
128 r.run();
130 finally {
131 nonCancelor.done();
135 public JComponent getProvidedFunComponent(Project project, String processId) {
136 for(ProgressFunComponentProvider provider: Extensions.getExtensions(ProgressFunComponentProvider.EP_NAME)) {
137 JComponent cmp = provider.getProgressFunComponent(project, processId);
138 if (cmp != null) return cmp;
141 for (ProgressFunComponentProvider provider : myFunComponentProviders) {
142 JComponent cmp = provider.getProgressFunComponent(project, processId);
143 if (cmp != null) return cmp;
145 return null;
148 public void setCancelButtonText(String cancelButtonText) {
149 ProgressIndicator progressIndicator = getProgressIndicator();
150 if (progressIndicator != null) {
151 if (progressIndicator instanceof SmoothProgressAdapter && cancelButtonText != null) {
152 ProgressIndicator original = ((SmoothProgressAdapter)progressIndicator).getOriginal();
153 if (original instanceof ProgressWindow) {
154 ((ProgressWindow)original).setCancelButtonText(cancelButtonText);
161 public void registerFunComponentProvider(ProgressFunComponentProvider provider) {
162 myFunComponentProviders.add(provider);
165 public void removeFunComponentProvider(ProgressFunComponentProvider provider) {
166 myFunComponentProviders.remove(provider);
169 public boolean hasProgressIndicator() {
170 return myCurrentProgressCount.get() > 0;
173 public boolean hasUnsafeProgressIndicator() {
174 return myCurrentUnsafeProgressCount.get() > 0;
177 public boolean hasModalProgressIndicator() {
178 return myCurrentModalProgressCount.get() > 0;
181 public void runProcess(@NotNull final Runnable process, final ProgressIndicator progress) {
182 executeProcessUnderProgress(new Runnable(){
183 public void run() {
184 synchronized (process) {
185 process.notify();
187 try {
188 if (progress != null && !progress.isRunning()) {
189 progress.start();
191 process.run();
193 finally {
194 if (progress != null && progress.isRunning()) {
195 progress.stop();
196 if (progress instanceof ProgressIndicatorEx) {
197 ((ProgressIndicatorEx)progress).processFinish();
202 },progress);
205 public void executeProcessUnderProgress(@NotNull Runnable process, ProgressIndicator progress) throws ProcessCanceledException {
206 ProgressIndicator oldIndicator = myThreadIndicator.get();
208 if (progress != null) myThreadIndicator.set(progress);
209 myCurrentProgressCount.incrementAndGet();
211 final boolean modal = progress != null && progress.isModal();
212 if (modal) myCurrentModalProgressCount.incrementAndGet();
213 if (progress == null || progress instanceof ProgressWindow) myCurrentUnsafeProgressCount.incrementAndGet();
215 try {
216 process.run();
218 finally {
219 myThreadIndicator.set(oldIndicator);
221 myCurrentProgressCount.decrementAndGet();
222 if (modal) myCurrentModalProgressCount.decrementAndGet();
223 if (progress == null || progress instanceof ProgressWindow) myCurrentUnsafeProgressCount.decrementAndGet();
227 public ProgressIndicator getProgressIndicator() {
228 return myThreadIndicator.get();
231 public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process, String progressTitle, boolean canBeCanceled, Project project) {
232 return runProcessWithProgressSynchronously(process, progressTitle, canBeCanceled, project, null);
235 public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process, String progressTitle, boolean canBeCanceled, Project project, JComponent parentComponent) {
236 Task.Modal task = new Task.Modal(project, progressTitle, canBeCanceled) {
237 public void run(@NotNull ProgressIndicator indicator) {
238 process.run();
241 return runProcessWithProgressSynchronously(task, parentComponent);
244 private static boolean runProcessWithProgressSynchronously(final Task task, final JComponent parentComponent) {
245 final long start = System.currentTimeMillis();
246 final boolean result = ((ApplicationEx)ApplicationManager.getApplication())
247 .runProcessWithProgressSynchronously(new TaskContainer(task) {
248 public void run() {
249 new TaskRunnable(task, ProgressManager.getInstance().getProgressIndicator()).run();
251 }, task.getTitle(), task.isCancellable(), task.getProject(), parentComponent, task.getCancelText());
252 if (result) {
253 final long end = System.currentTimeMillis();
254 final Task.NotificationInfo notificationInfo = task.getNotificationInfo();
255 if (notificationInfo != null && end - start > 5000) { // show notification only if process took more than 5 secs
256 final JFrame frame = WindowManager.getInstance().getFrame(task.getProject());
257 if (!frame.hasFocus()) {
258 systemNotify(notificationInfo);
262 task.onSuccess();
264 else {
265 task.onCancel();
267 return result;
270 private static void systemNotify(final Task.NotificationInfo notificationInfo) {
271 final SystemNotifications notifications = ServiceManager.getService(SystemNotifications.class);
272 if (notifications == null) return;
273 notifications.notify(notificationInfo.getNotificationName(), notificationInfo.getNotificationTitle(), notificationInfo.getNotificationText());
276 public void runProcessWithProgressAsynchronously(@NotNull Project project,
277 @NotNull String progressTitle,
278 @NotNull final Runnable process,
279 @Nullable final Runnable successRunnable,
280 @Nullable final Runnable canceledRunnable) {
281 runProcessWithProgressAsynchronously(project, progressTitle, process, successRunnable, canceledRunnable, PerformInBackgroundOption.DEAF);
284 public void runProcessWithProgressAsynchronously(@NotNull final Project project,
285 @Nls @NotNull final String progressTitle,
286 @NotNull final Runnable process,
287 @Nullable final Runnable successRunnable,
288 @Nullable final Runnable canceledRunnable,
289 @NotNull final PerformInBackgroundOption option) {
291 runProcessWithProgressAsynchronously(new Task.Backgroundable(project, progressTitle, true, option) {
292 public void run(@NotNull final ProgressIndicator indicator) {
293 process.run();
297 public void onCancel() {
298 if (canceledRunnable != null) {
299 canceledRunnable.run();
303 public void onSuccess() {
304 if (successRunnable != null) {
305 successRunnable.run();
311 public static void runProcessWithProgressAsynchronously(final Task.Backgroundable task) {
312 final ProgressIndicator progressIndicator;
313 if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
314 progressIndicator = new EmptyProgressIndicator();
316 else {
317 final BackgroundableProcessIndicator indicator = new BackgroundableProcessIndicator(task);
318 Disposer.register(ApplicationManager.getApplication(), indicator);
319 progressIndicator = indicator;
323 final Runnable process = new TaskRunnable(task, progressIndicator);
325 TaskContainer action = new TaskContainer(task) {
326 public void run() {
327 boolean canceled = false;
328 final long start = System.currentTimeMillis();
329 try {
330 ProgressManager.getInstance().runProcess(process, progressIndicator);
332 catch (ProcessCanceledException e) {
333 canceled = true;
335 final long end = System.currentTimeMillis();
337 if (canceled || progressIndicator.isCanceled()) {
338 ApplicationManager.getApplication().invokeLater(new Runnable() {
339 public void run() {
340 task.onCancel();
342 }, ModalityState.NON_MODAL);
344 else if (!canceled) {
345 final Task.NotificationInfo notificationInfo = task.getNotificationInfo();
346 if (notificationInfo != null && end - start > 5000) { // snow notification if process took more than 5 secs
347 final Component window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
348 if (window == null || notificationInfo.isShowWhenFocused()) {
349 systemNotify(notificationInfo);
353 ApplicationManager.getApplication().invokeLater(new Runnable() {
354 public void run() {
355 task.onSuccess();
357 }, ModalityState.NON_MODAL);
362 synchronized (process) {
363 ApplicationManager.getApplication().executeOnPooledThread(action);
364 try {
365 process.wait();
367 catch (InterruptedException e) {
368 throw new RuntimeException(e);
373 public void run(@NotNull final Task task) {
374 if (task.isHeadless()) {
375 new TaskRunnable(task, new EmptyProgressIndicator()).run();
376 return;
379 if (task.isModal()) {
380 runProcessWithProgressSynchronously(task.asModal(), null);
382 else {
383 final Task.Backgroundable backgroundable = task.asBackgroundable();
384 if (backgroundable.isConditionalModal() && !backgroundable.shouldStartInBackground()) {
385 runProcessWithProgressSynchronously(task, null);
387 else {
388 runProcessWithProgressAsynchronously(backgroundable);
393 private abstract static class TaskContainer implements Runnable {
394 private final Task myTask;
396 protected TaskContainer(final Task task) {
397 myTask = task;
400 public Task getTask() {
401 return myTask;
405 private static class TaskRunnable extends TaskContainer {
406 private final ProgressIndicator myIndicator;
408 private TaskRunnable(@NotNull Task task, @NotNull ProgressIndicator indicator) {
409 super(task);
410 myIndicator = indicator;
413 public void run() {
414 try {
415 getTask().run(myIndicator);
417 finally {
418 if (myIndicator instanceof ProgressIndicatorEx) {
419 ((ProgressIndicatorEx)myIndicator).finish(getTask());
425 //for debugging
426 @SuppressWarnings({"UnusedDeclaration"})
427 private static void stopCheckCanceled() {
428 Thread[] threads = new Thread[500];
429 Thread.enumerate(threads);
430 for (Thread thread : threads) {
431 if (thread == null) continue;
432 if (NAME.equals(thread.getName())) {
433 Thread.State oldState = thread.getState();
434 thread.suspend();
435 System.out.println(thread +" suspended ("+oldState+ "->"+thread.getState()+")");
440 @TestOnly
441 public static void setNeedToCheckCancel(boolean needToCheckCancel) {
442 ourNeedToCheckCancel = needToCheckCancel;