fighting the linux focus problem
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / progress / util / ProgressWindow.java
blobaad11d0c341acf0bdef3d57ce28b0d1e87c62425
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.util;
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.progress.ProgressManager;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.ui.DialogWrapper;
25 import com.intellij.openapi.ui.DialogWrapperPeer;
26 import com.intellij.openapi.ui.impl.FocusTrackbackProvider;
27 import com.intellij.openapi.ui.impl.GlassPaneDialogWrapperPeer;
28 import com.intellij.openapi.util.Comparing;
29 import com.intellij.openapi.util.Condition;
30 import com.intellij.openapi.util.Disposer;
31 import com.intellij.openapi.util.EmptyRunnable;
32 import com.intellij.openapi.wm.WindowManager;
33 import com.intellij.openapi.wm.IdeFocusManager;
34 import com.intellij.openapi.wm.ex.WindowManagerEx;
35 import com.intellij.ui.FocusTrackback;
36 import com.intellij.ui.PopupBorder;
37 import com.intellij.ui.TitlePanel;
38 import com.intellij.ui.awt.RelativePoint;
39 import com.intellij.util.Alarm;
40 import com.intellij.util.ui.UIUtil;
41 import org.jetbrains.annotations.Nullable;
43 import javax.swing.*;
44 import javax.swing.border.Border;
45 import java.awt.*;
46 import java.awt.event.*;
47 import java.io.File;
49 @SuppressWarnings({"NonStaticInitializer"})
50 public class ProgressWindow extends BlockingProgressIndicator implements Disposable {
51 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.util.ProgressWindow");
53 private static final int UPDATE_INTERVAL = 50; //msec. 20 frames per second.
55 private MyDialog myDialog;
56 private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
57 private final Alarm myInstallFunAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
58 private final Alarm myShowWindowAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
60 private final Project myProject;
61 private final boolean myShouldShowCancel;
62 private String myCancelText;
64 private String myTitle = null;
66 private boolean myStoppedAlready = false;
67 protected final FocusTrackback myFocusTrackback;
68 private boolean myStarted = false;
69 private boolean myBackgrounded = false;
70 private boolean myWasShown;
71 private String myProcessId = "<unknown>";
72 @Nullable private volatile Runnable myBackgroundHandler;
74 public ProgressWindow(boolean shouldShowCancel, Project project) {
75 this(shouldShowCancel, false, project);
78 public ProgressWindow(boolean shouldShowCancel, boolean shouldShowBackground, @Nullable Project project) {
79 this(shouldShowCancel, shouldShowBackground, project, null);
82 public ProgressWindow(boolean shouldShowCancel, boolean shouldShowBackground, @Nullable Project project, String cancelText) {
83 this(shouldShowCancel, shouldShowBackground, project, null, cancelText);
86 public ProgressWindow(boolean shouldShowCancel, boolean shouldShowBackground, @Nullable Project project, JComponent parentComponent, String cancelText) {
87 myProject = project;
88 myShouldShowCancel = shouldShowCancel;
89 myCancelText = cancelText;
90 setModalityProgress(shouldShowBackground ? null : this);
91 myFocusTrackback = new FocusTrackback(this, WindowManager.getInstance().suggestParentWindow(project), false);
93 Component parent = parentComponent;
94 if (parent == null && project == null) {
95 parent = JOptionPane.getRootFrame();
98 if (parent != null) {
99 myDialog = new MyDialog(shouldShowBackground, parent, myCancelText);
101 else {
102 myDialog = new MyDialog(shouldShowBackground, myProject, myCancelText);
105 Disposer.register(this, myDialog);
107 myFocusTrackback.registerFocusComponent(myDialog.getPanel());
110 public synchronized void start() {
111 LOG.assertTrue(!isRunning());
112 LOG.assertTrue(!myStoppedAlready);
114 super.start();
115 if (!ApplicationManager.getApplication().isUnitTestMode()) {
116 prepareShowDialog();
119 myStarted = true;
122 private synchronized boolean isStarted() {
123 return myStarted;
126 protected void prepareShowDialog() {
127 SwingUtilities.invokeLater(new Runnable() {
128 public void run() {
129 myShowWindowAlarm.addRequest(new Runnable() {
130 public void run() {
131 if (isRunning()) {
132 SwingUtilities.invokeLater(new Runnable() {
133 public void run() {
134 if (myDialog != null) {
135 final DialogWrapper popup = myDialog.myPopup;
136 if (popup != null) {
137 myFocusTrackback.registerFocusComponent(new FocusTrackback.ComponentQuery() {
138 public Component getComponent() {
139 return popup.getPreferredFocusedComponent();
142 if (popup.isShowing()) {
143 myWasShown = true;
149 showDialog();
151 else {
152 Disposer.dispose(ProgressWindow.this);
155 }, 300, getModalityState());
160 public void startBlocking() {
161 ApplicationManager.getApplication().assertIsDispatchThread();
162 LOG.assertTrue(!isRunning());
163 LOG.assertTrue(!myStoppedAlready);
165 enterModality();
167 IdeEventQueue.getInstance().pumpEventsForHierarchy(myDialog.myPanel, new Condition<AWTEvent>() {
168 public boolean value(final AWTEvent object) {
169 if (myShouldShowCancel &&
170 object instanceof KeyEvent &&
171 object.getID() == KeyEvent.KEY_PRESSED &&
172 ((KeyEvent)object).getKeyCode() == KeyEvent.VK_ESCAPE &&
173 ((KeyEvent)object).getModifiers() == 0) {
174 SwingUtilities.invokeLater(new Runnable() {
175 public void run() {
176 cancel();
180 return isStarted() && !isRunning();
184 exitModality();
187 public String getProcessId() {
188 return myProcessId;
191 public void setProcessId(final String processId) {
192 myProcessId = processId;
195 protected void showDialog() {
196 if (!isRunning() || isCanceled()) {
197 return;
200 if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
201 Runnable installer = new Runnable() {
202 public void run() {
203 if (isRunning() && !isCanceled() && getFraction() < 0.15 && myDialog!=null) {
204 final JComponent cmp = ProgressManager.getInstance().getProvidedFunComponent(myProject, getProcessId());
205 if (cmp != null) {
206 setFunComponent(cmp);
211 myInstallFunAlarm.addRequest(installer, 3000, getModalityState());
214 myWasShown = true;
215 myDialog.show();
216 if (myDialog != null) {
217 myDialog.myRepaintRunnable.run();
221 public void setIndeterminate(boolean indeterminate) {
222 super.setIndeterminate(indeterminate);
223 update();
226 public synchronized void stop() {
227 LOG.assertTrue(!myStoppedAlready);
228 myInstallFunAlarm.cancelAllRequests();
230 super.stop();
232 if (myDialog != null) {
233 myDialog.hide();
234 if (myDialog.wasShown()) {
235 myFocusTrackback.restoreFocus();
236 } else {
237 myFocusTrackback.consume();
241 myStoppedAlready = true;
243 Disposer.dispose(this);
245 SwingUtilities.invokeLater(EmptyRunnable.INSTANCE); // Just to give blocking dispatching a chance to go out.
248 public void cancel() {
249 super.cancel();
250 if (myDialog != null) {
251 myDialog.cancel();
255 public void background() {
256 final Runnable backgroundHandler = myBackgroundHandler;
257 if (backgroundHandler != null) {
258 backgroundHandler.run();
259 return;
262 if (myDialog != null) {
263 myBackgrounded = true;
264 myDialog.background();
266 if (myDialog.wasShown()) {
267 myFocusTrackback.restoreFocus();
269 else {
270 myFocusTrackback.consume();
273 myDialog = null;
277 public boolean isBackgrounded() {
278 return myBackgrounded;
281 public void setText(String text) {
282 if (!Comparing.equal(text, getText())) {
283 super.setText(text);
284 update();
288 public void setFraction(double fraction) {
289 if (fraction != getFraction()) {
290 super.setFraction(fraction);
291 update();
295 public void setText2(String text) {
296 if (!Comparing.equal(text, getText2())) {
297 super.setText2(text);
298 update();
302 private void update() {
303 if (myDialog != null) {
304 myDialog.update();
308 public void setTitle(String title) {
309 if (!Comparing.equal(title, myTitle)) {
310 myTitle = title;
311 update();
315 public String getTitle() {
316 return myTitle;
319 protected static int getPercentage(double fraction) {
320 return (int)(fraction * 99 + 0.5);
323 protected class MyDialog implements Disposable {
324 private long myLastTimeDrawn = -1;
325 private volatile boolean myShouldShowBackground;
327 private final Runnable myRepaintRunnable = new Runnable() {
328 public void run() {
329 String text = getText();
330 double fraction = getFraction();
331 String text2 = getText2();
333 myTextLabel.setText(text != null && text.length() > 0 ? text : " ");
334 if (!isIndeterminate() && fraction > 0) {
335 myPercentLabel.setText(getPercentage(fraction) + "%");
337 else {
338 myPercentLabel.setText(" ");
341 if (myProgressBar.isShowing()) {
342 final int perc = (int)(fraction * 100);
343 myProgressBar.setIndeterminate(perc == 0 || isIndeterminate());
344 myProgressBar.setValue(perc);
347 myText2Label.setText(getTitle2Text(text2, myText2Label.getWidth()));
349 myTitlePanel.setText(myTitle != null && myTitle.length() > 0 ? myTitle : " ");
351 myLastTimeDrawn = System.currentTimeMillis();
352 myRepaintedFlag = true;
356 private String getTitle2Text(String fullText, int labelWidth) {
357 if (fullText == null || fullText.length() == 0) return " ";
358 while (myText2Label.getFontMetrics(myText2Label.getFont()).stringWidth(fullText) > labelWidth) {
359 int sep = fullText.indexOf(File.separatorChar, 4);
360 if (sep < 0) return fullText;
361 fullText = "..." + fullText.substring(sep);
364 return fullText;
367 private final Runnable myUpdateRequest = new Runnable() {
368 public void run() {
369 update();
373 private JPanel myPanel;
375 private JLabel myTextLabel;
376 private JLabel myPercentLabel;
377 private JLabel myText2Label;
379 private JButton myCancelButton;
380 private JButton myBackgroundButton;
382 private JProgressBar myProgressBar;
383 private boolean myRepaintedFlag = true;
384 private JPanel myFunPanel;
385 private TitlePanel myTitlePanel;
386 private DialogWrapper myPopup;
387 private final Window myParentWindow;
388 private Point myLastClicked;
390 public MyDialog(boolean shouldShowBackground, Project project, String cancelText) {
391 Window parentWindow = WindowManager.getInstance().suggestParentWindow(project);
392 if (parentWindow == null) {
393 parentWindow = WindowManagerEx.getInstanceEx().getMostRecentFocusedWindow();
395 myParentWindow =parentWindow;
397 initDialog(shouldShowBackground, cancelText);
400 public MyDialog(boolean shouldShowBackground, Component parent, String cancelText) {
401 myParentWindow = parent instanceof Window
402 ? (Window)parent
403 : (Window)SwingUtilities.getAncestorOfClass(Window.class, parent);
404 initDialog(shouldShowBackground, cancelText);
407 private void initDialog(boolean shouldShowBackground, String cancelText) {
408 myFunPanel.setLayout(new BorderLayout());
409 myCancelButton.addActionListener(new ActionListener() {
410 public void actionPerformed(ActionEvent e) {
411 doCancelAction();
415 myCancelButton.registerKeyboardAction(new ActionListener() {
416 public void actionPerformed(ActionEvent e) {
417 if (myCancelButton.isEnabled()) {
418 doCancelAction();
421 }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
423 myShouldShowBackground = shouldShowBackground;
424 if (cancelText != null) {
425 setCancelButtonText(cancelText);
427 myProgressBar.setMaximum(100);
428 createCenterPanel();
430 myTitlePanel.setActive(true);
431 myTitlePanel.addMouseListener(new MouseAdapter() {
432 public void mousePressed(MouseEvent e) {
433 final Point titleOffset = RelativePoint.getNorthWestOf(myTitlePanel).getScreenPoint();
434 myLastClicked = new RelativePoint(e).getScreenPoint();
435 myLastClicked.x -= titleOffset.x;
436 myLastClicked.y -= titleOffset.y;
440 myTitlePanel.addMouseMotionListener(new MouseMotionAdapter() {
441 public void mouseDragged(MouseEvent e) {
442 if (myLastClicked == null) {
443 return;
445 final Point draggedTo = new RelativePoint(e).getScreenPoint();
446 draggedTo.x -= myLastClicked.x;
447 draggedTo.y -= myLastClicked.y;
449 if (myPopup != null) {
450 myPopup.setLocation(draggedTo);
457 public void dispose() {
458 UIUtil.disposeProgress(myProgressBar);
459 UIUtil.dispose(myTitlePanel);
462 public JPanel getPanel() {
463 return myPanel;
466 public void setShouldShowBackground(final boolean shouldShowBackground) {
467 myShouldShowBackground = shouldShowBackground;
468 SwingUtilities.invokeLater(new Runnable() {
469 public void run() {
470 myBackgroundButton.setVisible(shouldShowBackground);
471 myPanel.revalidate();
476 public void changeCancelButtonText(String text){
477 myCancelButton.setText(text);
480 public void doCancelAction() {
481 if (myShouldShowCancel) {
482 ProgressWindow.this.cancel();
486 public void cancel() {
487 if (myShouldShowCancel) {
488 myCancelButton.setEnabled(false);
492 private void createCenterPanel() {
493 // Cancel button (if any)
495 if (myCancelText != null) {
496 myCancelButton.setText(myCancelText);
498 myCancelButton.setVisible(myShouldShowCancel);
500 myBackgroundButton.setVisible(myShouldShowBackground);
501 myBackgroundButton.addActionListener(
502 new ActionListener() {
503 public void actionPerformed(ActionEvent e) {
504 if (myShouldShowBackground) {
505 ProgressWindow.this.background();
511 // Panel with progress indicator and percents
513 int width = myPercentLabel.getFontMetrics(myPercentLabel.getFont()).stringWidth("1000%");
514 myPercentLabel.setPreferredSize(new Dimension(width, myPercentLabel.getPreferredSize().height));
515 myPercentLabel.setHorizontalAlignment(SwingConstants.RIGHT);
518 private synchronized void update() {
519 if (myRepaintedFlag) {
520 if (System.currentTimeMillis() > myLastTimeDrawn + UPDATE_INTERVAL) {
521 myRepaintedFlag = false;
522 SwingUtilities.invokeLater(myRepaintRunnable);
524 else {
525 if (myUpdateAlarm.getActiveRequestCount() == 0) {
526 myUpdateAlarm.addRequest(myUpdateRequest, 500, getModalityState());
532 public synchronized void background() {
533 if (myShouldShowBackground) {
534 myBackgroundButton.setEnabled(false);
537 hide();
540 public void hide() {
541 SwingUtilities.invokeLater(new Runnable() {
542 public void run() {
543 if (myPopup != null) {
544 myPopup.close(DialogWrapper.CANCEL_EXIT_CODE);
545 myPopup = null;
551 public void show() {
552 if (ApplicationManager.getApplication().isHeadlessEnvironment()) return;
553 if (myParentWindow == null) return;
554 if (myPopup != null) {
555 myPopup.close(DialogWrapper.CANCEL_EXIT_CODE);
558 myPopup = myParentWindow.isShowing() ? new MyDialogWrapper(myParentWindow, myShouldShowCancel) : new MyDialogWrapper(myProject, myShouldShowCancel);
559 myPopup.setUndecorated(true);
561 SwingUtilities.invokeLater(new Runnable() {
562 public void run() {
563 if (myPopup != null) {
564 if (myPopup.getPeer() instanceof FocusTrackbackProvider) {
565 final FocusTrackback focusTrackback = ((FocusTrackbackProvider)myPopup.getPeer()).getFocusTrackback();
566 if (focusTrackback != null) {
567 focusTrackback.consume();
571 getFocusManager().requestFocus(myCancelButton, true);
576 myPopup.show();
579 public boolean wasShown() {
580 return myWasShown;
583 private class MyDialogWrapper extends DialogWrapper {
584 private boolean myIsCancellable;
586 public MyDialogWrapper(Project project, final boolean cancellable) {
587 super(project, false);
588 init();
589 myIsCancellable = cancellable;
592 public MyDialogWrapper(Component parent, final boolean cancellable) {
593 super(parent, false);
594 init();
595 myIsCancellable = cancellable;
598 @Override
599 public void doCancelAction() {
600 if (myIsCancellable) {
601 super.doCancelAction();
605 @Override
606 protected DialogWrapperPeer createPeer(final Component parent, final boolean canBeParent) {
607 if (System.getProperty("vintage.progress") == null) {
608 try {
609 return new GlassPaneDialogWrapperPeer(this, parent, canBeParent);
611 catch (GlassPaneDialogWrapperPeer.GlasspanePeerUnavailableException e) {
612 return super.createPeer(parent, canBeParent);
614 } else {
615 return super.createPeer(parent, canBeParent);
619 @Override
620 protected DialogWrapperPeer createPeer(final boolean canBeParent, final boolean toolkitModalIfPossible) {
621 if (System.getProperty("vintage.progress") == null) {
622 try {
623 return new GlassPaneDialogWrapperPeer(this, canBeParent);
625 catch (GlassPaneDialogWrapperPeer.GlasspanePeerUnavailableException e) {
626 return super.createPeer(canBeParent, toolkitModalIfPossible);
628 } else {
629 return super.createPeer(canBeParent, toolkitModalIfPossible);
633 @Override
634 protected DialogWrapperPeer createPeer(final Project project, final boolean canBeParent) {
635 if (System.getProperty("vintage.progress") == null) {
636 try {
637 return new GlassPaneDialogWrapperPeer(this, project, canBeParent);
639 catch (GlassPaneDialogWrapperPeer.GlasspanePeerUnavailableException e) {
640 return super.createPeer(project, canBeParent);
642 } else {
643 return super.createPeer(project, canBeParent);
647 protected void init() {
648 super.init();
649 setUndecorated(true);
650 myPanel.setBorder(PopupBorder.Factory.create(true));
653 protected boolean isProgressDialog() {
654 return true;
657 protected JComponent createCenterPanel() {
658 return myPanel;
661 @Nullable
662 protected JComponent createSouthPanel() {
663 return null;
666 @Nullable
667 protected Border createContentPaneBorder() {
668 return null;
673 public void setBackgroundHandler(@Nullable Runnable backgroundHandler) {
674 myBackgroundHandler = backgroundHandler;
675 myDialog.setShouldShowBackground(backgroundHandler != null);
678 public void setCancelButtonText(String text){
679 if (myDialog != null) {
680 myDialog.changeCancelButtonText(text);
682 else {
683 myCancelText = text;
687 private void setFunComponent(JComponent c) {
688 myDialog.myFunPanel.removeAll();
689 if (c != null) {
690 myDialog.myFunPanel.add(new JSeparator(), BorderLayout.NORTH);
691 myDialog.myFunPanel.add(c, BorderLayout.CENTER);
694 if (myDialog.myPopup != null && !(myDialog.myPopup.getPeer() instanceof GlassPaneDialogWrapperPeer)) { // TODO[spL]: remove
695 final Window wnd = SwingUtilities.windowForComponent(myDialog.myPanel);
696 if (wnd != null) { // Can be null if just hidden
697 wnd.pack();
699 } else {
700 myDialog.myPopup.validate();
704 private IdeFocusManager getFocusManager() {
705 return IdeFocusManager.getInstance(myProject);
708 public void dispose() {