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
.wm
.impl
.status
;
18 import com
.intellij
.idea
.ActionsBundle
;
19 import com
.intellij
.openapi
.application
.ApplicationManager
;
20 import com
.intellij
.openapi
.editor
.Editor
;
21 import com
.intellij
.openapi
.progress
.TaskInfo
;
22 import com
.intellij
.openapi
.ui
.MessageType
;
23 import com
.intellij
.openapi
.ui
.popup
.Balloon
;
24 import com
.intellij
.openapi
.ui
.popup
.BalloonHandler
;
25 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
26 import com
.intellij
.openapi
.util
.MultiValuesMap
;
27 import com
.intellij
.openapi
.wm
.StatusBar
;
28 import com
.intellij
.openapi
.wm
.ex
.ProgressIndicatorEx
;
29 import com
.intellij
.ui
.awt
.RelativePoint
;
30 import com
.intellij
.ui
.components
.labels
.LinkLabel
;
31 import com
.intellij
.ui
.components
.labels
.LinkListener
;
32 import com
.intellij
.ui
.components
.panels
.Wrapper
;
33 import com
.intellij
.util
.Alarm
;
34 import com
.intellij
.util
.ui
.AbstractLayoutManager
;
35 import com
.intellij
.util
.ui
.AsyncProcessIcon
;
36 import com
.intellij
.util
.ui
.update
.MergingUpdateQueue
;
37 import com
.intellij
.util
.ui
.update
.Update
;
38 import org
.jetbrains
.annotations
.NotNull
;
41 import javax
.swing
.border
.Border
;
42 import javax
.swing
.border
.CompoundBorder
;
43 import javax
.swing
.border
.EmptyBorder
;
44 import javax
.swing
.event
.HyperlinkListener
;
46 import java
.awt
.event
.MouseAdapter
;
47 import java
.awt
.event
.MouseEvent
;
48 import java
.util
.ArrayList
;
49 import java
.util
.Collection
;
50 import java
.util
.HashMap
;
53 public class InfoAndProgressPanel
extends JPanel
implements StatusBarPatch
{
54 private final ProcessPopup myPopup
;
55 private final TextPanel myInfoPanel
= new TextPanel(true);
57 private final ArrayList
<ProgressIndicatorEx
> myOriginals
= new ArrayList
<ProgressIndicatorEx
>();
58 private final ArrayList
<TaskInfo
> myInfos
= new ArrayList
<TaskInfo
>();
59 private final Map
<InlineProgressIndicator
, ProgressIndicatorEx
> myInline2Original
60 = new HashMap
<InlineProgressIndicator
, ProgressIndicatorEx
>();
61 private final MultiValuesMap
<ProgressIndicatorEx
, InlineProgressIndicator
> myOriginal2Inlines
62 = new MultiValuesMap
<ProgressIndicatorEx
, InlineProgressIndicator
>();
64 private final MergingUpdateQueue myUpdateQueue
;
65 private final AsyncProcessIcon myProgressIcon
;
66 private final Alarm myQueryAlarm
= new Alarm(Alarm
.ThreadToUse
.SWING_THREAD
);
68 private boolean myShouldClosePopupAndOnProcessFinish
;
69 private final CompoundBorder myCompoundBorder
;
71 public InfoAndProgressPanel(final StatusBar statusBar
) {
73 final Border emptyBorder
= BorderFactory
.createEmptyBorder(0, 2, 0, 2);
74 myInfoPanel
.setBorder(emptyBorder
);
75 myInfoPanel
.setOpaque(false);
77 myCompoundBorder
= BorderFactory
.createCompoundBorder(new StatusBarImpl
.SeparatorBorder
.Left(), new EmptyBorder(0, 2, 0, 2));
79 myProgressIcon
= new AsyncProcessIcon("Background process");
80 myProgressIcon
.setOpaque(true);
82 myProgressIcon
.addMouseListener(new MouseAdapter() {
84 public void mousePressed(MouseEvent e
) {
85 if (!myPopup
.isShowing()) {
91 myProgressIcon
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
93 StatusBarTooltipper
.install(this, myProgressIcon
, statusBar
);
95 myUpdateQueue
= new MergingUpdateQueue("Progress indicator", 50, true, MergingUpdateQueue
.ANY_COMPONENT
);
96 myPopup
= new ProcessPopup(this);
101 public JComponent
getComponent() {
105 public String
updateStatusBar(final Editor selected
, final JComponent componentSelected
) {
106 return ActionsBundle
.message("action.ShowProcessWindow.double.click");
109 public void clear() {
113 public void addProgress(final ProgressIndicatorEx original
, TaskInfo info
) {
114 synchronized (myOriginals
) {
115 final boolean veryFirst
= myOriginals
.isEmpty();
117 myOriginals
.add(original
);
120 final InlineProgressIndicator expanded
= createInlineDelegate(info
, original
, false);
121 final InlineProgressIndicator compact
= createInlineDelegate(info
, original
, true);
123 myPopup
.addIndicator(expanded
);
124 myProgressIcon
.resume();
126 if (veryFirst
&& !myPopup
.isShowing()) {
127 buildInInlineIndicator(compact
);
130 buildInProcessCount();
137 private void removeProgress(InlineProgressIndicator progress
) {
138 synchronized (myOriginals
) {
139 if (!myInline2Original
.containsKey(progress
)) return;
141 final boolean last
= myOriginals
.size() == 1;
142 final boolean beforeLast
= myOriginals
.size() == 2;
144 myPopup
.removeIndicator(progress
);
146 final ProgressIndicatorEx original
= removeFromMaps(progress
);
147 if (myOriginals
.contains(original
)) return;
150 restoreEmptyStatus();
151 if (myShouldClosePopupAndOnProcessFinish
) {
156 if (myPopup
.isShowing() || myOriginals
.size() > 1) {
157 buildInProcessCount();
159 else if (beforeLast
) {
160 buildInInlineIndicator(createInlineDelegate(myInfos
.get(0), myOriginals
.get(0), true));
163 restoreEmptyStatus();
171 private ProgressIndicatorEx
removeFromMaps(final InlineProgressIndicator progress
) {
172 final ProgressIndicatorEx original
= myInline2Original
.get(progress
);
174 myInline2Original
.remove(progress
);
176 myOriginal2Inlines
.remove(original
, progress
);
177 if (myOriginal2Inlines
.get(original
) == null) {
178 final int originalIndex
= myOriginals
.indexOf(original
);
179 myOriginals
.remove(originalIndex
);
180 myInfos
.remove(originalIndex
);
186 private void openProcessPopup() {
187 synchronized (myOriginals
) {
188 if (myPopup
.isShowing()) return;
189 if (!myOriginals
.isEmpty()) {
190 myShouldClosePopupAndOnProcessFinish
= true;
191 buildInProcessCount();
194 myShouldClosePopupAndOnProcessFinish
= false;
195 restoreEmptyStatus();
201 void hideProcessPopup() {
202 synchronized (myOriginals
) {
203 if (!myPopup
.isShowing()) return;
205 if (myOriginals
.size() == 1) {
206 buildInInlineIndicator(createInlineDelegate(myInfos
.get(0), myOriginals
.get(0), true));
208 else if (myOriginals
.isEmpty()) {
209 restoreEmptyStatus();
212 buildInProcessCount();
219 private void buildInProcessCount() {
221 setLayout(new BorderLayout());
223 final JPanel progressCountPanel
= new JPanel(new BorderLayout(0, 2));
224 String processWord
= myOriginals
.size() == 1 ?
" process" : " processes";
225 final LinkLabel label
= new LinkLabel(myOriginals
.size() + processWord
+ " running...", null, new LinkListener() {
226 public void linkSelected(final LinkLabel aSource
, final Object aLinkData
) {
227 triggerPopupShowing();
230 label
.setOpaque(true);
232 final Wrapper labelComp
= new Wrapper(label
);
233 progressCountPanel
.add(labelComp
, BorderLayout
.CENTER
);
235 myProgressIcon
.setBorder(myCompoundBorder
);
236 progressCountPanel
.add(myProgressIcon
, BorderLayout
.WEST
);
238 add(myInfoPanel
, BorderLayout
.CENTER
);
240 progressCountPanel
.setBorder(new EmptyBorder(0, 0, 0, 4));
241 add(progressCountPanel
, BorderLayout
.EAST
);
247 private void buildInInlineIndicator(final InlineProgressIndicator inline
) {
249 setLayout(new InlineLayout());
252 final JPanel inlinePanel
= new JPanel(new BorderLayout());
254 inline
.getComponent().setBorder(new EmptyBorder(0, 0, 0, 2));
255 inlinePanel
.add(inline
.getComponent(), BorderLayout
.CENTER
);
257 myProgressIcon
.setBorder(myCompoundBorder
);
258 inlinePanel
.add(myProgressIcon
, BorderLayout
.WEST
);
260 inline
.updateProgressNow();
264 myInfoPanel
.revalidate();
265 myInfoPanel
.repaint();
268 public void setText(final String text
) {
269 myInfoPanel
.setText(text
);
272 public BalloonHandler
notifyByBalloon(MessageType type
, String htmlBody
, Icon icon
, HyperlinkListener listener
) {
273 final Balloon balloon
= JBPopupFactory
.getInstance().createHtmlTextBalloonBuilder(
274 htmlBody
.replace("\n", "<br>"),
275 icon
!= null ? icon
: type
.getDefaultIcon(),
276 type
.getPopupBackground(),
277 listener
).createBalloon();
279 SwingUtilities
.invokeLater(new Runnable() {
281 Component comp
= InfoAndProgressPanel
.this;
282 if (comp
.isShowing()) {
283 int offset
= comp
.getHeight() / 2;
284 Point point
= new Point(comp
.getWidth() - offset
, comp
.getHeight() - offset
);
285 balloon
.show(new RelativePoint(comp
, point
), Balloon
.Position
.above
);
290 return new BalloonHandler() {
292 SwingUtilities
.invokeLater(new Runnable() {
301 private static class InlineLayout
extends AbstractLayoutManager
{
303 public Dimension
preferredLayoutSize(final Container parent
) {
304 Dimension result
= new Dimension();
305 for (int i
= 0; i
< parent
.getComponentCount(); i
++) {
306 final Dimension prefSize
= parent
.getComponent(i
).getPreferredSize();
307 result
.width
+= prefSize
.width
;
308 result
.height
= Math
.max(prefSize
.height
, result
.height
);
313 public void layoutContainer(final Container parent
) {
314 final Dimension size
= parent
.getSize();
315 int compWidth
= size
.width
/ parent
.getComponentCount();
317 for (int i
= 0; i
< parent
.getComponentCount(); i
++) {
318 final Component each
= parent
.getComponent(i
);
319 if (i
== parent
.getComponentCount() - 1) {
320 compWidth
= size
.width
- eachX
;
322 each
.setBounds(eachX
, 0, compWidth
, size
.height
);
328 private InlineProgressIndicator
createInlineDelegate(final TaskInfo info
, final ProgressIndicatorEx original
, final boolean compact
) {
329 final Collection
<InlineProgressIndicator
> inlines
= myOriginal2Inlines
.get(original
);
330 if (inlines
!= null) {
331 for (InlineProgressIndicator eachInline
: inlines
) {
332 if (eachInline
.isCompact() == compact
) return eachInline
;
336 final InlineProgressIndicator inline
= new MyInlineProgressIndicator(compact
, info
, original
);
338 myInline2Original
.put(inline
, original
);
339 myOriginal2Inlines
.put(original
, inline
);
342 inline
.getComponent().addMouseListener(new MouseAdapter() {
344 public void mousePressed(MouseEvent e
) {
345 if (!myPopup
.isShowing()) {
355 private void triggerPopupShowing() {
356 if (myPopup
.isShowing()) {
364 private void restoreEmptyStatus() {
366 setLayout(new BorderLayout());
367 add(myInfoPanel
, BorderLayout
.CENTER
);
368 myProgressIcon
.setBorder(myCompoundBorder
);
369 add(myProgressIcon
, BorderLayout
.EAST
);
370 myProgressIcon
.suspend();
371 myInfoPanel
.revalidate();
372 myInfoPanel
.repaint();
375 public boolean isProcessWindowOpen() {
376 return myPopup
.isShowing();
379 public void setProcessWindowOpen(final boolean open
) {
388 private class MyInlineProgressIndicator
extends InlineProgressIndicator
{
389 private final ProgressIndicatorEx myOriginal
;
390 private final TaskInfo myTask
;
392 public MyInlineProgressIndicator(final boolean compact
, final TaskInfo task
, final ProgressIndicatorEx original
) {
393 super(compact
, task
);
394 myOriginal
= original
;
396 original
.addStateDelegate(this);
399 public void cancel() {
411 protected boolean isFinished() {
412 return isFinished(myTask
);
416 public void finish(@NotNull final TaskInfo task
) {
418 queueRunningUpdate(new Runnable() {
420 removeProgress(MyInlineProgressIndicator
.this);
426 protected void cancelRequest() {
430 protected void queueProgressUpdate(final Runnable update
) {
431 myUpdateQueue
.queue(new Update(MyInlineProgressIndicator
.this, false, 1) {
433 ApplicationManager
.getApplication().invokeLater(update
);
438 protected void queueRunningUpdate(final Runnable update
) {
439 myUpdateQueue
.queue(new Update(new Object(), false, 0) {
441 ApplicationManager
.getApplication().invokeLater(update
);
447 private void runQuery() {
448 if (getRootPane() == null) return;
450 synchronized (myOriginals
) {
451 for (InlineProgressIndicator each
: myInline2Original
.keySet()) {
452 each
.updateProgress();
455 myQueryAlarm
.addRequest(new Runnable() {