IDEA-144699: rename
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / progress / util / ProgressWindow.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.progress.util;
17
18 import com.intellij.ide.IdeEventQueue;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.ui.DialogWrapper;
24 import com.intellij.openapi.util.Comparing;
25 import com.intellij.openapi.util.Condition;
26 import com.intellij.openapi.util.Disposer;
27 import com.intellij.openapi.util.EmptyRunnable;
28 import com.intellij.openapi.wm.IdeFocusManager;
29 import com.intellij.openapi.wm.WindowManager;
30 import com.intellij.ui.FocusTrackback;
31 import com.intellij.util.messages.Topic;
32 import com.intellij.util.ui.UIUtil;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.*;
37 import java.awt.*;
38 import java.awt.event.ActionEvent;
39 import java.awt.event.ActionListener;
40 import java.awt.event.KeyEvent;
41
42 @SuppressWarnings({"NonStaticInitializer"})
43 public class ProgressWindow extends ProgressIndicatorBase implements BlockingProgressIndicator, Disposable {
44   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.util.ProgressWindow");
45
46   /**
47    * This constant defines default delay for showing progress dialog (in millis).
48    *
49    * @see #setDelayInMillis(int)
50    */
51   public static final int DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS = 300;
52
53   private ProgressDialog myDialog;
54
55   final Project myProject;
56   final boolean myShouldShowCancel;
57   String myCancelText;
58
59   private String myTitle = null;
60
61   private boolean myStoppedAlready = false;
62   protected final FocusTrackback myFocusTrackback;
63   private boolean myStarted      = false;
64   protected boolean myBackgrounded = false;
65   private String myProcessId = "<unknown>";
66   @Nullable private volatile Runnable myBackgroundHandler;
67   private int myDelayInMillis = DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS;
68
69   public interface Listener {
70     void progressWindowCreated(ProgressWindow pw);
71   }
72
73   public static final Topic<Listener> TOPIC = Topic.create("progress window", Listener.class);
74
75   public ProgressWindow(boolean shouldShowCancel, Project project) {
76     this(shouldShowCancel, false, project);
77   }
78
79   public ProgressWindow(boolean shouldShowCancel, boolean shouldShowBackground, @Nullable Project project) {
80     this(shouldShowCancel, shouldShowBackground, project, null);
81   }
82
83   public ProgressWindow(boolean shouldShowCancel, boolean shouldShowBackground, @Nullable Project project, String cancelText) {
84     this(shouldShowCancel, shouldShowBackground, project, null, cancelText);
85   }
86
87   public ProgressWindow(boolean shouldShowCancel,
88                         boolean shouldShowBackground,
89                         @Nullable Project project,
90                         JComponent parentComponent,
91                         String cancelText) {
92     myProject = project;
93     myShouldShowCancel = shouldShowCancel;
94     myCancelText = cancelText;
95     setModalityProgress(shouldShowBackground ? null : this);
96     myFocusTrackback = new FocusTrackback(this, WindowManager.getInstance().suggestParentWindow(project), false);
97
98     Component parent = parentComponent;
99     if (parent == null && project == null && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
100       parent = JOptionPane.getRootFrame();
101     }
102
103     if (parent == null) {
104       myDialog = new ProgressDialog(this, shouldShowBackground, myProject, myCancelText);
105     }
106     else {
107       myDialog = new ProgressDialog(this, shouldShowBackground, parent, myCancelText);
108     }
109
110     Disposer.register(this, myDialog);
111
112     myFocusTrackback.registerFocusComponent(myDialog.getPanel());
113     addStateDelegate(new AbstractProgressIndicatorExBase() {
114       @Override
115       public void cancel() {
116         super.cancel();
117         if (myDialog != null) {
118           myDialog.cancel();
119         }
120       }
121     });
122     ApplicationManager.getApplication().getMessageBus().syncPublisher(TOPIC).progressWindowCreated(this);
123   }
124
125   @Override
126   public synchronized void start() {
127     LOG.assertTrue(!isRunning());
128     LOG.assertTrue(!myStoppedAlready);
129
130     super.start();
131     if (!ApplicationManager.getApplication().isUnitTestMode()) {
132       prepareShowDialog();
133     }
134
135     myStarted = true;
136   }
137
138   /**
139    * There is a possible case that many short (in terms of time) progress tasks are executed in a small amount of time.
140    * Problem: UI blinks and looks ugly if we show progress dialog for every such task (every dialog disappears shortly).
141    * Solution is to postpone showing progress dialog in assumption that the task may be already finished when it's
142    * time to show the dialog.
143    * <p/>
144    * Default value is {@link #DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS}
145    *
146    * @param delayInMillis   new delay time in milliseconds
147    */
148   public void setDelayInMillis(int delayInMillis) {
149     myDelayInMillis = delayInMillis;
150   }
151
152   private synchronized boolean isStarted() {
153     return myStarted;
154   }
155
156   protected void prepareShowDialog() {
157     // We know at least about one use-case that requires special treatment here: many short (in terms of time) progress tasks are
158     // executed in a small amount of time. Problem: UI blinks and looks ugly if we show progress dialog that disappears shortly
159     // for each of them. Solution is to postpone the tasks of showing progress dialog. Hence, it will not be shown at all
160     // if the task is already finished when the time comes.
161     Timer timer = UIUtil.createNamedTimer("Progress window timer",myDelayInMillis, new ActionListener() {
162       @Override
163       public void actionPerformed(ActionEvent e) {
164         ApplicationManager.getApplication().invokeLater(new Runnable() {
165           @Override
166           public void run() {
167             if (isRunning()) {
168               if (myDialog != null) {
169                 final DialogWrapper popup = myDialog.myPopup;
170                 if (popup != null) {
171                   myFocusTrackback.registerFocusComponent(new FocusTrackback.ComponentQuery() {
172                     @Override
173                     public Component getComponent() {
174                       return popup.getPreferredFocusedComponent();
175                     }
176                   });
177                   if (popup.isShowing()) {
178                     myDialog.myWasShown = true;
179                   }
180                 }
181               }
182               showDialog();
183             }
184             else {
185               Disposer.dispose(ProgressWindow.this);
186             }
187           }
188         }, getModalityState());
189       }
190     });
191     timer.setRepeats(false);
192     timer.start();
193   }
194
195   @Override
196   public void startBlocking() {
197     startBlocking(EmptyRunnable.getInstance());
198   }
199
200   public void startBlocking(@NotNull Runnable init) {
201     ApplicationManager.getApplication().assertIsDispatchThread();
202     synchronized (this) {
203       LOG.assertTrue(!isRunning());
204       LOG.assertTrue(!myStoppedAlready);
205     }
206
207     enterModality();
208     init.run();
209
210     IdeEventQueue.getInstance().pumpEventsForHierarchy(myDialog.myPanel, new Condition<AWTEvent>() {
211       @Override
212       public boolean value(final AWTEvent object) {
213         if (myShouldShowCancel &&
214             object instanceof KeyEvent &&
215             object.getID() == KeyEvent.KEY_PRESSED &&
216             ((KeyEvent)object).getKeyCode() == KeyEvent.VK_ESCAPE &&
217             ((KeyEvent)object).getModifiers() == 0) {
218           SwingUtilities.invokeLater(new Runnable() {
219             @Override
220             public void run() {
221               cancel();
222             }
223           });
224         }
225         return isStarted() && !isRunning();
226       }
227     });
228
229     exitModality();
230   }
231
232   @NotNull
233   public String getProcessId() {
234     return myProcessId;
235   }
236
237   public void setProcessId(@NotNull String processId) {
238     myProcessId = processId;
239   }
240
241   protected void showDialog() {
242     if (!isRunning() || isCanceled()) {
243       return;
244     }
245
246     myDialog.show();
247     if (myDialog != null) {
248       myDialog.myRepaintRunnable.run();
249     }
250   }
251
252   @Override
253   public void startNonCancelableSection() {
254     if (isCancelable()) {
255       enableCancel(false);
256     }
257     super.startNonCancelableSection();
258   }
259
260   @Override
261   public void finishNonCancelableSection() {
262     super.finishNonCancelableSection();
263     if (isCancelable()) {
264       enableCancel(true);
265     }
266   }
267
268   @Override
269   public void setIndeterminate(boolean indeterminate) {
270     super.setIndeterminate(indeterminate);
271     update();
272   }
273
274   @Override
275   public synchronized void stop() {
276     LOG.assertTrue(!myStoppedAlready);
277
278     super.stop();
279
280     if (isDialogShowing()) {
281       if (myFocusTrackback != null) {
282         myFocusTrackback.setWillBeSheduledForRestore();
283       }
284     }
285
286     UIUtil.invokeLaterIfNeeded(new Runnable() {
287       @Override
288       public void run() {
289         boolean wasShowing = isDialogShowing();
290         if (myDialog != null) {
291           myDialog.hide();
292         }
293
294         if (myFocusTrackback != null) {
295           if (wasShowing) {
296             myFocusTrackback.restoreFocus();
297           }
298           else {
299             myFocusTrackback.consume();
300           }
301         }
302
303         synchronized (ProgressWindow.this) {
304           myStoppedAlready = true;
305         }
306
307         Disposer.dispose(ProgressWindow.this);
308       }
309     });
310
311     SwingUtilities.invokeLater(EmptyRunnable.INSTANCE); // Just to give blocking dispatching a chance to go out.
312   }
313
314   private boolean isDialogShowing() {
315     return myDialog != null && myDialog.getPanel() != null && myDialog.getPanel().isShowing();
316   }
317
318   public void background() {
319     final Runnable backgroundHandler = myBackgroundHandler;
320     if (backgroundHandler != null) {
321       backgroundHandler.run();
322       return;
323     }
324
325     if (myDialog != null) {
326       myBackgrounded = true;
327       myDialog.background();
328
329       if (myDialog.wasShown()) {
330         myFocusTrackback.restoreFocus();
331       }
332       else {
333         myFocusTrackback.consume();
334       }
335
336       myDialog = null;
337     }
338   }
339
340   public boolean isBackgrounded() {
341     return myBackgrounded;
342   }
343
344   @Override
345   public void setText(String text) {
346     if (!Comparing.equal(text, getText())) {
347       super.setText(text);
348       update();
349     }
350   }
351
352   @Override
353   public void setFraction(double fraction) {
354     if (fraction != getFraction()) {
355       super.setFraction(fraction);
356       update();
357     }
358   }
359
360   @Override
361   public void setText2(String text) {
362     if (!Comparing.equal(text, getText2())) {
363       super.setText2(text);
364       update();
365     }
366   }
367
368   private void update() {
369     if (myDialog != null) {
370       myDialog.update();
371     }
372   }
373
374   public void setTitle(String title) {
375     if (!Comparing.equal(title, myTitle)) {
376       myTitle = title;
377       update();
378     }
379   }
380
381   public String getTitle() {
382     return myTitle;
383   }
384
385   public void setBackgroundHandler(@Nullable Runnable backgroundHandler) {
386     myBackgroundHandler = backgroundHandler;
387     myDialog.setShouldShowBackground(backgroundHandler != null);
388   }
389
390   public void setCancelButtonText(String text) {
391     if (myDialog != null) {
392       myDialog.changeCancelButtonText(text);
393     }
394     else {
395       myCancelText = text;
396     }
397   }
398
399   IdeFocusManager getFocusManager() {
400     return IdeFocusManager.getInstance(myProject);
401   }
402
403   @Override
404   public void dispose() {
405     stopSystemActivity();
406   }
407
408   @Override
409   public boolean isPopupWasShown() {
410     return myDialog != null && myDialog.myPopup != null && myDialog.myPopup.isShowing();
411   }
412
413   protected void enableCancel(boolean enable) {
414     if (myDialog != null) {
415       myDialog.enableCancelButtonIfNeeded(enable);
416     }
417   }
418 }