Line 1
Link Here
|
|
|
1 |
/* |
2 |
* The contents of this file are subject to the terms of the Common Development |
3 |
* and Distribution License (the License). You may not use this file except in |
4 |
* compliance with the License. |
5 |
* |
6 |
* You can obtain a copy of the License at http://www.netbeans.org/cddl.html |
7 |
* or http://www.netbeans.org/cddl.txt. |
8 |
* |
9 |
* When distributing Covered Code, include this CDDL Header Notice in each file |
10 |
* and include the License file at http://www.netbeans.org/cddl.txt. |
11 |
* If applicable, add the following below the CDDL Header, with the fields |
12 |
* enclosed by brackets [] replaced by your own identifying information: |
13 |
* "Portions Copyrighted [year] [name of copyright owner]" |
14 |
* |
15 |
* The Original Software is NetBeans. The Initial Developer of the Original |
16 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun |
17 |
* Microsystems, Inc. All Rights Reserved. |
18 |
*/ |
19 |
|
20 |
|
21 |
package org.netbeans.modules.progress.ui; |
22 |
import java.awt.AWTEvent; |
23 |
import java.awt.Component; |
24 |
import java.awt.Container; |
25 |
import java.awt.Cursor; |
26 |
import java.awt.Dimension; |
27 |
import java.awt.FlowLayout; |
28 |
import java.awt.Frame; |
29 |
import java.awt.Point; |
30 |
import java.awt.Rectangle; |
31 |
import java.awt.Toolkit; |
32 |
import java.awt.event.AWTEventListener; |
33 |
import java.awt.event.ActionEvent; |
34 |
import java.awt.event.ComponentAdapter; |
35 |
import java.awt.event.ComponentEvent; |
36 |
import java.awt.event.KeyEvent; |
37 |
import java.awt.event.MouseAdapter; |
38 |
import java.awt.event.MouseEvent; |
39 |
import java.awt.event.MouseListener; |
40 |
import java.awt.event.WindowEvent; |
41 |
import java.awt.event.WindowStateListener; |
42 |
import java.util.HashMap; |
43 |
import java.util.Map; |
44 |
import javax.swing.AbstractAction; |
45 |
import javax.swing.Action; |
46 |
import javax.swing.BorderFactory; |
47 |
import javax.swing.JComponent; |
48 |
import javax.swing.JLabel; |
49 |
import javax.swing.JPanel; |
50 |
import javax.swing.JPopupMenu; |
51 |
import javax.swing.JSeparator; |
52 |
import javax.swing.JWindow; |
53 |
import javax.swing.KeyStroke; |
54 |
import javax.swing.Popup; |
55 |
import javax.swing.SwingUtilities; |
56 |
import javax.swing.event.ListDataEvent; |
57 |
import javax.swing.event.ListDataListener; |
58 |
import javax.swing.event.ListSelectionEvent; |
59 |
import javax.swing.event.ListSelectionListener; |
60 |
import org.netbeans.progress.spi.InternalHandle; |
61 |
import org.netbeans.progress.spi.ProgressUIWorker; |
62 |
import org.netbeans.progress.spi.ProgressEvent; |
63 |
import org.netbeans.progress.module.ProgressListAction; |
64 |
import org.netbeans.progress.spi.ProgressUIWorkerWithModel; |
65 |
import org.netbeans.progress.spi.TaskModel; |
66 |
import org.openide.DialogDisplayer; |
67 |
import org.openide.NotifyDescriptor; |
68 |
import org.openide.util.NbBundle; |
69 |
import org.openide.util.Utilities; |
70 |
import org.openide.windows.WindowManager; |
71 |
|
72 |
|
73 |
/** |
74 |
* |
75 |
* @author Milos Kleint (mkleint@netbeans.org) |
76 |
*/ |
77 |
public class StatusLineComponent extends JPanel implements ProgressUIWorkerWithModel { |
78 |
private NbProgressBar bar; |
79 |
private JLabel label; |
80 |
private JSeparator separator; |
81 |
private InternalHandle handle; |
82 |
private boolean showingPopup = false; |
83 |
private TaskModel model; |
84 |
private MouseListener mouseListener; |
85 |
private HideAWTListener hideListener; |
86 |
private Popup popup; |
87 |
private JWindow popupWindow; |
88 |
private PopupPane pane; |
89 |
private Map<InternalHandle, ListComponent> handleComponentMap; |
90 |
private final int prefferedHeight; |
91 |
/** Creates a new instance of StatusLineComponent */ |
92 |
public StatusLineComponent() { |
93 |
handleComponentMap = new HashMap<InternalHandle, ListComponent>(); |
94 |
label = new JLabel(); |
95 |
label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
96 |
bar = new NbProgressBar(); |
97 |
bar.setUseInStatusBar(true); |
98 |
// tricks to figure out correct height. |
99 |
bar.setStringPainted(true); |
100 |
bar.setString("XXX"); |
101 |
label.setText("XXX"); |
102 |
prefferedHeight = Math.max(label.getPreferredSize().height, bar.getPreferredSize().height) + 2; |
103 |
|
104 |
bar.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
105 |
// setBorder(BorderFactory.createLineBorder(Color.BLUE, 1)); |
106 |
FlowLayout flay = new FlowLayout(); |
107 |
flay.setVgap(1); |
108 |
flay.setHgap(5); |
109 |
setLayout(flay); |
110 |
// HACK - put smaller font inside the progress bar to keep |
111 |
// the height of the progressbar constant for determinate and indeterminate bars |
112 |
// Font fnt = UIManager.getFont("ProgressBar.font"); |
113 |
// bar.setFont(fnt.deriveFont(fnt.getStyle(), fnt.getSize() - 3)); |
114 |
|
115 |
mouseListener = new MListener(); |
116 |
label.addMouseListener(mouseListener); |
117 |
bar.addMouseListener(mouseListener); |
118 |
addMouseListener(mouseListener); |
119 |
hideListener = new HideAWTListener(); |
120 |
pane = new PopupPane(); |
121 |
pane.getActionMap().put("HidePopup", new AbstractAction() { |
122 |
public void actionPerformed(ActionEvent actionEvent) { |
123 |
// System.out.println("escape pressed - hiding"); |
124 |
hidePopup(); |
125 |
} |
126 |
}); |
127 |
pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "HidePopup"); |
128 |
pane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "HidePopup"); |
129 |
separator = new JSeparator(JSeparator.VERTICAL); |
130 |
separator.setPreferredSize(new Dimension(5, prefferedHeight)); |
131 |
separator.setBorder(BorderFactory.createEmptyBorder(1, 0, 2, 0)); |
132 |
} |
133 |
|
134 |
public Dimension getPreferredSize() { |
135 |
Dimension retValue; |
136 |
retValue = super.getPreferredSize(); |
137 |
retValue.height = prefferedHeight; |
138 |
return retValue; |
139 |
} |
140 |
|
141 |
public Dimension getMinimumSize() { |
142 |
Dimension retValue; |
143 |
retValue = super.getMinimumSize(); |
144 |
retValue.height = prefferedHeight; |
145 |
return retValue; |
146 |
} |
147 |
|
148 |
public Dimension getMaximumSize() { |
149 |
Dimension retValue; |
150 |
retValue = super.getMaximumSize(); |
151 |
retValue.height = prefferedHeight; |
152 |
return retValue; |
153 |
} |
154 |
|
155 |
public void setModel(TaskModel mod) { |
156 |
model = mod; |
157 |
model.addListDataListener(new Listener()); |
158 |
model.addListSelectionListener(new ListSelectionListener() { |
159 |
public void valueChanged(ListSelectionEvent e) { |
160 |
pane.updateBoldFont(model.getSelectedHandle()); |
161 |
} |
162 |
}); |
163 |
} |
164 |
|
165 |
private void setTooltipForAll() { |
166 |
int size = model.getSize(); |
167 |
String key = "NbProgressBar.tooltip1"; //NOI18N |
168 |
if (size == 1) { |
169 |
key = "NbProgressBar.tooltip2"; //NOI18N |
170 |
} |
171 |
String text = NbBundle.getMessage(StatusLineComponent.class, key, new Integer(size)); |
172 |
setToolTipText(text); |
173 |
label.setToolTipText(text); |
174 |
bar.setToolTipText(text); |
175 |
} |
176 |
|
177 |
public void processProgressEvent(ProgressEvent event) { |
178 |
if (event.getType() == ProgressEvent.TYPE_START) { |
179 |
createListItem(event.getSource()); |
180 |
} else if (event.getType() == ProgressEvent.TYPE_PROGRESS || |
181 |
event.getType() == ProgressEvent.TYPE_SWITCH) { |
182 |
ListComponent comp = (ListComponent)handleComponentMap.get(event.getSource()); |
183 |
if (comp == null) { |
184 |
createListItem(event.getSource()); |
185 |
comp = (ListComponent)handleComponentMap.get(event.getSource()); |
186 |
} |
187 |
comp.processProgressEvent(event); |
188 |
} else if (event.getType() == ProgressEvent.TYPE_FINISH) { |
189 |
removeListItem(event.getSource()); |
190 |
if (model.getSelectedHandle() != null && handle != model.getSelectedHandle()) { |
191 |
initiateComponent(model.getSelectedHandle().requestStateSnapshot()); |
192 |
} |
193 |
} |
194 |
|
195 |
} |
196 |
|
197 |
public void processSelectedProgressEvent(ProgressEvent event) { |
198 |
if (event.getType() == ProgressEvent.TYPE_START) { |
199 |
initiateComponent(event); |
200 |
return; |
201 |
} else if (event.getType() == ProgressEvent.TYPE_FINISH) { |
202 |
//happens only when there's no more handles. |
203 |
hidePopup(); |
204 |
removeAll(); |
205 |
//#63393, 61940 fix - removeAll() just invalidates. seems to work without revalidate/repaint on some platforms, fail on others. |
206 |
revalidate(); |
207 |
repaint(); |
208 |
return; |
209 |
} else { |
210 |
if (event.getSource() != handle || event.isSwitched()) { |
211 |
initiateComponent(event); |
212 |
} |
213 |
if (event.getWorkunitsDone() > 0) { |
214 |
bar.setValue(event.getWorkunitsDone()); |
215 |
} |
216 |
bar.setString(getBarString(event.getPercentageDone(), event.getEstimatedCompletion())); |
217 |
if (event.getDisplayName() != null) { |
218 |
label.setText(event.getDisplayName()); |
219 |
} |
220 |
} |
221 |
} |
222 |
|
223 |
static String formatEstimate(long estimate) { |
224 |
long minutes = estimate / 60; |
225 |
long seconds = estimate - (minutes * 60); |
226 |
return "" + minutes + (seconds < 10 ? ":0" : ":") + seconds; |
227 |
} |
228 |
|
229 |
static String getBarString(int percentage, long estimatedCompletion) { |
230 |
if (estimatedCompletion != -1) { |
231 |
return formatEstimate(estimatedCompletion); |
232 |
} |
233 |
if (percentage != -1) { |
234 |
return "" + percentage + "%"; |
235 |
} |
236 |
return ""; |
237 |
} |
238 |
|
239 |
private void initiateComponent(ProgressEvent event) { |
240 |
handle = event.getSource(); |
241 |
boolean toShow = false; |
242 |
label.setText(handle.getDisplayName()); |
243 |
if (label.getParent() == null) { |
244 |
add(label); |
245 |
toShow = true; |
246 |
} |
247 |
NbProgressBar.setupBar(event.getSource(), bar); |
248 |
if (bar.getParent() == null) { |
249 |
add(bar); |
250 |
toShow = true; |
251 |
} |
252 |
if (separator.getParent() == null) { |
253 |
add(separator); |
254 |
toShow = true; |
255 |
} |
256 |
if (toShow) { |
257 |
revalidate(); |
258 |
repaint(); |
259 |
} |
260 |
} |
261 |
|
262 |
private class Listener implements ListDataListener { |
263 |
public void intervalAdded(ListDataEvent e) { |
264 |
setTooltipForAll(); |
265 |
} |
266 |
|
267 |
public void intervalRemoved(ListDataEvent e) { |
268 |
setTooltipForAll(); |
269 |
} |
270 |
|
271 |
|
272 |
public void contentsChanged(ListDataEvent e) { |
273 |
setTooltipForAll(); |
274 |
} |
275 |
} |
276 |
|
277 |
public void hidePopup() { |
278 |
if (popupWindow != null) { |
279 |
// popupWindow.getContentPane().removeAll(); |
280 |
popupWindow.setVisible(false); |
281 |
} |
282 |
Toolkit.getDefaultToolkit().removeAWTEventListener(hideListener); |
283 |
WindowManager.getDefault().getMainWindow().removeWindowStateListener(hideListener); |
284 |
WindowManager.getDefault().getMainWindow().removeComponentListener(hideListener); |
285 |
showingPopup = false; |
286 |
} |
287 |
|
288 |
private void createListItem(InternalHandle handle) { |
289 |
ListComponent comp; |
290 |
if (handleComponentMap.containsKey(handle)) { |
291 |
// happens when we click to display on popup and there is a |
292 |
// new handle waiting in the queue. |
293 |
comp = handleComponentMap.get(handle); |
294 |
} else { |
295 |
comp = new ListComponent(handle); |
296 |
handleComponentMap.put(handle, comp); |
297 |
} |
298 |
pane.addListComponent(comp); |
299 |
pane.updateBoldFont(model.getSelectedHandle()); |
300 |
if (showingPopup) { |
301 |
resizePopup(); |
302 |
} |
303 |
} |
304 |
|
305 |
private void removeListItem(InternalHandle handle) { |
306 |
handleComponentMap.remove(handle); |
307 |
pane.removeListComponent(handle); |
308 |
pane.updateBoldFont(model.getSelectedHandle()); |
309 |
if (showingPopup) { |
310 |
resizePopup(); |
311 |
} |
312 |
} |
313 |
|
314 |
|
315 |
public void showPopup() { |
316 |
if (showingPopup) { |
317 |
return; |
318 |
} |
319 |
InternalHandle[] handles = model.getHandles(); |
320 |
if (handles.length == 0) { |
321 |
// just in case.. |
322 |
return; |
323 |
} |
324 |
showingPopup = true; |
325 |
|
326 |
// NOT using PopupFactory |
327 |
// 1. on linux, creates mediumweight popup taht doesn't refresh behind visible glasspane |
328 |
// 2. on mac, needs an owner frame otherwise hiding tooltip also hides the popup. (linux requires no owner frame to force heavyweight) |
329 |
// 3. the created window is not focusable window |
330 |
if (popupWindow == null) { |
331 |
popupWindow = new JWindow(WindowManager.getDefault().getMainWindow()); |
332 |
popupWindow.getContentPane().add(pane); |
333 |
} |
334 |
Toolkit.getDefaultToolkit().addAWTEventListener(hideListener, AWTEvent.MOUSE_EVENT_MASK); |
335 |
WindowManager.getDefault().getMainWindow().addWindowStateListener(hideListener); |
336 |
WindowManager.getDefault().getMainWindow().addComponentListener(hideListener); |
337 |
resizePopup(); |
338 |
popupWindow.setVisible(true); |
339 |
pane.requestFocus(); |
340 |
// System.out.println(" window focusable=" + popupWindow.isFocusableWindow()); |
341 |
} |
342 |
|
343 |
private void resizePopup() { |
344 |
popupWindow.pack(); |
345 |
Point point = new Point(0,0); |
346 |
SwingUtilities.convertPointToScreen(point, this); |
347 |
Dimension dim = popupWindow.getSize(); |
348 |
//#63265 |
349 |
Rectangle usableRect = Utilities.getUsableScreenBounds(); |
350 |
Point loc = new Point(point.x + this.getSize().width + 4 - dim.width , point.y - dim.height - 5); |
351 |
if (! usableRect.contains(loc)) { |
352 |
loc = new Point(loc.x, point.y + 5 + this.getSize().height); |
353 |
} |
354 |
// +4 here because of the width of the close button in popup, we |
355 |
// want the progress bars to align visually.. but there's separator in status now.. |
356 |
popupWindow.setLocation(loc); |
357 |
// System.out.println("count=" + count); |
358 |
// System.out.println("offset =" + offset); |
359 |
} |
360 |
|
361 |
private class HideAWTListener extends ComponentAdapter implements AWTEventListener, WindowStateListener { |
362 |
public void eventDispatched(java.awt.AWTEvent aWTEvent) { |
363 |
if (aWTEvent instanceof MouseEvent) { |
364 |
MouseEvent mv = (MouseEvent)aWTEvent; |
365 |
if (mv.getClickCount() > 0) { |
366 |
Component comp = (Component)aWTEvent.getSource(); |
367 |
Container par = SwingUtilities.getAncestorNamed("progresspopup", comp); //NOI18N |
368 |
Container barpar = SwingUtilities.getAncestorOfClass(StatusLineComponent.class, comp); |
369 |
if (par == null && barpar == null) { |
370 |
hidePopup(); |
371 |
} |
372 |
} |
373 |
} |
374 |
} |
375 |
|
376 |
public void windowStateChanged(WindowEvent windowEvent) { |
377 |
if (showingPopup) { |
378 |
int oldState = windowEvent.getOldState(); |
379 |
int newState = windowEvent.getNewState(); |
380 |
|
381 |
if (((oldState & Frame.ICONIFIED) == 0) && |
382 |
((newState & Frame.ICONIFIED) == Frame.ICONIFIED)) { |
383 |
hidePopup(); |
384 |
// } else if (((oldState & Frame.ICONIFIED) == Frame.ICONIFIED) && |
385 |
// ((newState & Frame.ICONIFIED) == 0 )) { |
386 |
// //TODO remember we showed before and show again? I guess not worth the efford, not part of spec. |
387 |
} |
388 |
} |
389 |
|
390 |
} |
391 |
|
392 |
public void componentResized(ComponentEvent evt) { |
393 |
if (showingPopup) { |
394 |
resizePopup(); |
395 |
} |
396 |
} |
397 |
|
398 |
public void componentMoved(ComponentEvent evt) { |
399 |
if (showingPopup) { |
400 |
resizePopup(); |
401 |
} |
402 |
} |
403 |
|
404 |
} |
405 |
|
406 |
private class MListener extends MouseAdapter { |
407 |
public void mouseClicked(java.awt.event.MouseEvent e) { |
408 |
if (e.getButton() != MouseEvent.BUTTON1) { |
409 |
showMenu(e); |
410 |
} else { |
411 |
if (showingPopup) { |
412 |
hidePopup(); |
413 |
} else { |
414 |
showPopup(); |
415 |
} |
416 |
} |
417 |
} |
418 |
|
419 |
} |
420 |
|
421 |
private void showMenu(MouseEvent e) { |
422 |
JPopupMenu popup = new JPopupMenu(); |
423 |
popup.add(new ProgressListAction(NbBundle.getMessage(StatusLineComponent.class, "StatusLineComponent.ShowProcessList"))); |
424 |
popup.add(new ViewAction()); |
425 |
popup.add(new CancelAction()); |
426 |
popup.show((Component)e.getSource(), e.getX(), e.getY()); |
427 |
} |
428 |
|
429 |
private class CancelAction extends AbstractAction { |
430 |
public CancelAction() { |
431 |
putValue(Action.NAME, NbBundle.getMessage(StatusLineComponent.class, "StatusLineComponent.Cancel")); |
432 |
setEnabled(handle == null ? false : handle.isAllowCancel()); |
433 |
|
434 |
} |
435 |
public void actionPerformed(ActionEvent actionEvent) { |
436 |
InternalHandle hndl = handle; |
437 |
if (hndl !=null && hndl.getState() == InternalHandle.STATE_RUNNING) { |
438 |
String message = NbBundle.getMessage(StatusLineComponent.class, "Cancel_Question", handle.getDisplayName()); |
439 |
String title = NbBundle.getMessage(StatusLineComponent.class, "Cancel_Question_Title"); |
440 |
NotifyDescriptor dd = new NotifyDescriptor(message, title, |
441 |
NotifyDescriptor.YES_NO_OPTION, |
442 |
NotifyDescriptor.QUESTION_MESSAGE, null, null); |
443 |
Object retType = DialogDisplayer.getDefault().notify(dd); |
444 |
if (retType == NotifyDescriptor.YES_OPTION && hndl.getState() == InternalHandle.STATE_RUNNING) { |
445 |
hndl.requestCancel(); |
446 |
} |
447 |
} |
448 |
} |
449 |
} |
450 |
|
451 |
private class ViewAction extends AbstractAction { |
452 |
public ViewAction() { |
453 |
putValue(Action.NAME, NbBundle.getMessage(StatusLineComponent.class, "StatusLineComponent.View")); |
454 |
setEnabled(handle == null ? false : handle.isAllowView()); |
455 |
|
456 |
} |
457 |
public void actionPerformed(ActionEvent actionEvent) { |
458 |
if (handle != null) { |
459 |
handle.requestView(); |
460 |
} |
461 |
} |
462 |
} |
463 |
|
464 |
|
465 |
} |