001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.progress.ui;
043:
044: import java.awt.AWTEvent;
045: import java.awt.Component;
046: import java.awt.Container;
047: import java.awt.Cursor;
048: import java.awt.Dimension;
049: import java.awt.FlowLayout;
050: import java.awt.Frame;
051: import java.awt.Graphics;
052: import java.awt.Image;
053: import java.awt.Point;
054: import java.awt.Rectangle;
055: import java.awt.Toolkit;
056: import java.awt.event.AWTEventListener;
057: import java.awt.event.ActionEvent;
058: import java.awt.event.ComponentAdapter;
059: import java.awt.event.ComponentEvent;
060: import java.awt.event.KeyEvent;
061: import java.awt.event.MouseAdapter;
062: import java.awt.event.MouseEvent;
063: import java.awt.event.MouseListener;
064: import java.awt.event.WindowEvent;
065: import java.awt.event.WindowStateListener;
066: import java.util.HashMap;
067: import java.util.Map;
068: import javax.swing.AbstractAction;
069: import javax.swing.Action;
070: import javax.swing.BorderFactory;
071: import javax.swing.Icon;
072: import javax.swing.ImageIcon;
073: import javax.swing.JButton;
074: import javax.swing.JComponent;
075: import javax.swing.JLabel;
076: import javax.swing.JPanel;
077: import javax.swing.JPopupMenu;
078: import javax.swing.JSeparator;
079: import javax.swing.JWindow;
080: import javax.swing.KeyStroke;
081: import javax.swing.Popup;
082: import javax.swing.SwingUtilities;
083: import javax.swing.UIManager;
084: import javax.swing.event.ListDataEvent;
085: import javax.swing.event.ListDataListener;
086: import javax.swing.event.ListSelectionEvent;
087: import javax.swing.event.ListSelectionListener;
088: import org.netbeans.progress.spi.InternalHandle;
089: import org.netbeans.progress.spi.ProgressEvent;
090: import org.netbeans.progress.module.ProgressListAction;
091: import org.netbeans.progress.spi.ProgressUIWorkerWithModel;
092: import org.netbeans.progress.spi.TaskModel;
093: import org.openide.DialogDisplayer;
094: import org.openide.NotifyDescriptor;
095: import org.openide.util.NbBundle;
096: import org.openide.util.Utilities;
097: import org.openide.windows.WindowManager;
098:
099: /**
100: *
101: * @author Milos Kleint (mkleint@netbeans.org)
102: */
103: public class StatusLineComponent extends JPanel implements
104: ProgressUIWorkerWithModel {
105: private NbProgressBar bar;
106: private JLabel label;
107: private JSeparator separator;
108: private InternalHandle handle;
109: private boolean showingPopup = false;
110: private TaskModel model;
111: private MouseListener mouseListener;
112: private HideAWTListener hideListener;
113: private Popup popup;
114: private JWindow popupWindow;
115: private PopupPane pane;
116: private Map<InternalHandle, ListComponent> handleComponentMap;
117: private final int prefferedHeight;
118: private JButton closeButton;
119:
120: /** Creates a new instance of StatusLineComponent */
121: public StatusLineComponent() {
122: handleComponentMap = new HashMap<InternalHandle, ListComponent>();
123: FlowLayout flay = new FlowLayout();
124: flay.setVgap(1);
125: flay.setHgap(5);
126: setLayout(flay);
127: mouseListener = new MListener();
128: addMouseListener(mouseListener);
129: hideListener = new HideAWTListener();
130:
131: createLabel();
132: createBar();
133: // tricks to figure out correct height.
134: bar.setStringPainted(true);
135: bar.setString("XXX");
136: label.setText("XXX");
137: prefferedHeight = Math.max(label.getPreferredSize().height, bar
138: .getPreferredSize().height) + 2;
139:
140: discardLabel();
141: discardBar();
142:
143: pane = new PopupPane();
144: pane.getActionMap().put("HidePopup", new AbstractAction() {
145: public void actionPerformed(ActionEvent actionEvent) {
146: // System.out.println("escape pressed - hiding");
147: hidePopup();
148: }
149: });
150: pane.getInputMap().put(
151: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
152: "HidePopup");
153: pane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
154: .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
155: "HidePopup");
156:
157: }
158:
159: private void createLabel() {
160: discardLabel();
161: label = new JLabel();
162: label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
163: label.addMouseListener(mouseListener);
164: }
165:
166: private void discardLabel() {
167: if (label != null) {
168: label.removeMouseListener(mouseListener);
169: label = null;
170: }
171: }
172:
173: private void createBar() {
174: discardBar();
175: bar = new NbProgressBar();
176: bar.setUseInStatusBar(true);
177: bar.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
178: // setBorder(BorderFactory.createLineBorder(Color.BLUE, 1));
179: // HACK - put smaller font inside the progress bar to keep
180: // the height of the progressbar constant for determinate and indeterminate bars
181: // Font fnt = UIManager.getFont("ProgressBar.font");
182: // bar.setFont(fnt.deriveFont(fnt.getStyle(), fnt.getSize() - 3));
183: bar.addMouseListener(mouseListener);
184:
185: }
186:
187: private void discardBar() {
188: if (bar != null) {
189: bar.removeMouseListener(mouseListener);
190: bar = null;
191: }
192: }
193:
194: private void createCloseButton() {
195: discardCloseButton();
196: closeButton = new JButton();
197: closeButton.setBorderPainted(false);
198: closeButton.setBorder(BorderFactory.createEmptyBorder());
199: closeButton.setOpaque(false);
200: closeButton.setContentAreaFilled(false);
201:
202: Image img = (Image) UIManager.get("nb.progress.cancel.icon");
203: if (null != img) {
204: closeButton.setIcon(new ImageIcon(img));
205: }
206: img = (Image) UIManager
207: .get("nb.progress.cancel.icon.mouseover");
208: if (null != img) {
209: closeButton.setRolloverEnabled(true);
210: closeButton.setRolloverIcon(new ImageIcon(img));
211: }
212: img = (Image) UIManager.get("nb.progress.cancel.icon.pressed");
213: if (null != img) {
214: closeButton.setPressedIcon(new ImageIcon(img));
215: }
216: closeButton.setToolTipText(NbBundle.getMessage(
217: ListComponent.class, "ListComponent.btnClose.tooltip"));
218: }
219:
220: private void discardCloseButton() {
221: closeButton = null;
222: }
223:
224: private void createSeparator() {
225: discardSeparator();
226: separator = new JSeparator(JSeparator.VERTICAL);
227: // separator.setPreferredSize(new Dimension(5, prefferedHeight));
228: separator
229: .setBorder(BorderFactory.createEmptyBorder(1, 0, 2, 0));
230: }
231:
232: private void discardSeparator() {
233: separator = null;
234: }
235:
236: public Dimension getPreferredSize() {
237: Dimension retValue;
238: retValue = super .getPreferredSize();
239: retValue.height = prefferedHeight;
240: return retValue;
241: }
242:
243: public Dimension getMinimumSize() {
244: Dimension retValue;
245: retValue = super .getMinimumSize();
246: retValue.height = prefferedHeight;
247: return retValue;
248: }
249:
250: public Dimension getMaximumSize() {
251: Dimension retValue;
252: retValue = super .getMaximumSize();
253: retValue.height = prefferedHeight;
254: return retValue;
255: }
256:
257: public void setModel(TaskModel mod) {
258: model = mod;
259: model.addListDataListener(new Listener());
260: model.addListSelectionListener(new ListSelectionListener() {
261: public void valueChanged(ListSelectionEvent e) {
262: pane.updateBoldFont(model.getSelectedHandle());
263: }
264: });
265: }
266:
267: private void setTooltipForAll() {
268: int size = model.getSize();
269: String key = "NbProgressBar.tooltip1"; //NOI18N
270: if (size == 1) {
271: key = "NbProgressBar.tooltip2"; //NOI18N
272: }
273: String text = NbBundle.getMessage(StatusLineComponent.class,
274: key, new Integer(size));
275: setToolTipText(text);
276: if (label != null) {
277: label.setToolTipText(text);
278: }
279: if (bar != null) {
280: bar.setToolTipText(text);
281: }
282: }
283:
284: public void processProgressEvent(ProgressEvent event) {
285: if (event.getType() == ProgressEvent.TYPE_START) {
286: createListItem(event.getSource());
287: } else if (event.getType() == ProgressEvent.TYPE_PROGRESS
288: || event.getType() == ProgressEvent.TYPE_SWITCH
289: || event.getType() == ProgressEvent.TYPE_SILENT) {
290: ListComponent comp = (ListComponent) handleComponentMap
291: .get(event.getSource());
292: if (comp == null) {
293: createListItem(event.getSource());
294: comp = (ListComponent) handleComponentMap.get(event
295: .getSource());
296: }
297: comp.processProgressEvent(event);
298: } else if (event.getType() == ProgressEvent.TYPE_FINISH) {
299: removeListItem(event.getSource());
300: if (model.getSelectedHandle() != null
301: && handle != model.getSelectedHandle()) {
302: ProgressEvent snap = model.getSelectedHandle()
303: .requestStateSnapshot();
304: initiateComponent(snap);
305: if (snap.getSource().isInSleepMode()) {
306: bar.setString(snap.getMessage());
307: }
308:
309: }
310: }
311:
312: }
313:
314: public void processSelectedProgressEvent(ProgressEvent event) {
315: if (event.getType() == ProgressEvent.TYPE_START) {
316: initiateComponent(event);
317: return;
318: } else if (event.getType() == ProgressEvent.TYPE_FINISH) {
319: //happens only when there's no more handles.
320: hidePopup();
321: removeAll();
322: discardSeparator();
323: discardCloseButton();
324: discardBar();
325: discardLabel();
326: //#63393, 61940 fix - removeAll() just invalidates. seems to work without revalidate/repaint on some platforms, fail on others.
327: revalidate();
328: repaint();
329: return;
330: } else {
331: if (event.getSource() != handle
332: || event.isSwitched()
333: || event.getType() == ProgressEvent.TYPE_SILENT
334: ||
335: // the following condition re-initiates the bar when going from/to sleep mode..
336: (event.getSource().isInSleepMode() != (bar
337: .getClientProperty(NbProgressBar.SLEEPY) != null))) { //NIO18N
338: initiateComponent(event);
339: }
340: if (event.getWorkunitsDone() > 0) {
341: bar.setValue(event.getWorkunitsDone());
342: }
343: bar.setString(getBarString(event.getPercentageDone(), event
344: .getEstimatedCompletion()));
345: if (event.getDisplayName() != null) {
346: label.setText(event.getDisplayName());
347: }
348: if (event.getSource().isInSleepMode()) {
349: bar.setString(event.getMessage());
350: }
351:
352: }
353: }
354:
355: static String formatEstimate(long estimate) {
356: long minutes = estimate / 60;
357: long seconds = estimate - (minutes * 60);
358: return "" + minutes + (seconds < 10 ? ":0" : ":") + seconds;
359: }
360:
361: static String getBarString(double percentage,
362: long estimatedCompletion) {
363: if (estimatedCompletion != -1) {
364: return formatEstimate(estimatedCompletion);
365: }
366: if (percentage != -1) {
367: int rounded = (int) Math.round(percentage);
368: if (rounded > 100) {
369: rounded = 100;
370: }
371: return "" + rounded + "%"; //NOI18N
372: }
373: return "";
374: }
375:
376: private void initiateComponent(ProgressEvent event) {
377: handle = event.getSource();
378: boolean toShow = false;
379: if (label == null) {
380: createLabel();
381: add(label);
382: toShow = true;
383: label.setToolTipText(getToolTipText());
384: }
385: label.setText(handle.getDisplayName());
386:
387: if (bar == null) {
388: createBar();
389: add(bar);
390: toShow = true;
391: bar.setToolTipText(getToolTipText());
392:
393: }
394: NbProgressBar.setupBar(event.getSource(), bar);
395:
396: if (closeButton == null) {
397: createCloseButton();
398: add(closeButton);
399: toShow = true;
400: }
401: if (separator == null) {
402: createSeparator();
403: add(separator);
404: toShow = true;
405: }
406: if (handle.isAllowCancel()) {
407: closeButton.setAction(new CancelAction(false));
408: } else {
409: closeButton.setAction(new EmptyCancelAction());
410: }
411: if (toShow) {
412: revalidate();
413: repaint();
414: }
415: }
416:
417: private class Listener implements ListDataListener {
418: public void intervalAdded(ListDataEvent e) {
419: setTooltipForAll();
420: }
421:
422: public void intervalRemoved(ListDataEvent e) {
423: setTooltipForAll();
424: }
425:
426: public void contentsChanged(ListDataEvent e) {
427: setTooltipForAll();
428: }
429: }
430:
431: public void hidePopup() {
432: if (popupWindow != null) {
433: // popupWindow.getContentPane().removeAll();
434: popupWindow.setVisible(false);
435: }
436: Toolkit.getDefaultToolkit()
437: .removeAWTEventListener(hideListener);
438: WindowManager.getDefault().getMainWindow()
439: .removeWindowStateListener(hideListener);
440: WindowManager.getDefault().getMainWindow()
441: .removeComponentListener(hideListener);
442: showingPopup = false;
443: }
444:
445: private void createListItem(InternalHandle handle) {
446: ListComponent comp;
447: if (handleComponentMap.containsKey(handle)) {
448: // happens when we click to display on popup and there is a
449: // new handle waiting in the queue.
450: comp = handleComponentMap.get(handle);
451: } else {
452: comp = new ListComponent(handle);
453: handleComponentMap.put(handle, comp);
454: }
455: pane.addListComponent(comp);
456: pane.updateBoldFont(model.getSelectedHandle());
457: if (showingPopup) {
458: resizePopup();
459: }
460: }
461:
462: private void removeListItem(InternalHandle handle) {
463: handleComponentMap.remove(handle);
464: pane.removeListComponent(handle);
465: pane.updateBoldFont(model.getSelectedHandle());
466: if (showingPopup) {
467: resizePopup();
468: }
469: }
470:
471: public void showPopup() {
472: if (showingPopup) {
473: return;
474: }
475: InternalHandle[] handles = model.getHandles();
476: if (handles.length == 0) {
477: // just in case..
478: return;
479: }
480: showingPopup = true;
481:
482: // NOT using PopupFactory
483: // 1. on linux, creates mediumweight popup taht doesn't refresh behind visible glasspane
484: // 2. on mac, needs an owner frame otherwise hiding tooltip also hides the popup. (linux requires no owner frame to force heavyweight)
485: // 3. the created window is not focusable window
486: if (popupWindow == null) {
487: popupWindow = new JWindow(WindowManager.getDefault()
488: .getMainWindow());
489: popupWindow.getContentPane().add(pane);
490: }
491: Toolkit.getDefaultToolkit().addAWTEventListener(hideListener,
492: AWTEvent.MOUSE_EVENT_MASK);
493: WindowManager.getDefault().getMainWindow()
494: .addWindowStateListener(hideListener);
495: WindowManager.getDefault().getMainWindow()
496: .addComponentListener(hideListener);
497: resizePopup();
498: popupWindow.setVisible(true);
499: pane.requestFocus();
500: // System.out.println(" window focusable=" + popupWindow.isFocusableWindow());
501: }
502:
503: private void resizePopup() {
504: popupWindow.pack();
505: Point point = new Point(0, 0);
506: SwingUtilities.convertPointToScreen(point, this );
507: Dimension dim = popupWindow.getSize();
508: //#63265
509: Rectangle usableRect = Utilities.getUsableScreenBounds();
510: Point loc = new Point(point.x + this .getSize().width
511: - dim.width - separator.getSize().width - 5 * 2,
512: point.y - dim.height - 5);
513: // -5 in x coordinate is becuase of the hgap between the separator and button and separator and edge
514: if (!usableRect.contains(loc)) {
515: loc = new Point(loc.x, point.y + 5 + this .getSize().height);
516: }
517: // +4 here because of the width of the close button in popup, we
518: // want the progress bars to align visually.. but there's separator in status now..
519: popupWindow.setLocation(loc);
520: // System.out.println("count=" + count);
521: // System.out.println("offset =" + offset);
522: }
523:
524: private class HideAWTListener extends ComponentAdapter implements
525: AWTEventListener, WindowStateListener {
526: public void eventDispatched(java.awt.AWTEvent aWTEvent) {
527: if (aWTEvent instanceof MouseEvent) {
528: MouseEvent mv = (MouseEvent) aWTEvent;
529: if (mv.getClickCount() > 0) {
530: //#118828
531: if (!(aWTEvent.getSource() instanceof Component)) {
532: return;
533: }
534: Component comp = (Component) aWTEvent.getSource();
535: Container par = SwingUtilities.getAncestorNamed(
536: "progresspopup", comp); //NOI18N
537: Container barpar = SwingUtilities
538: .getAncestorOfClass(
539: StatusLineComponent.class, comp);
540: if (par == null && barpar == null) {
541: hidePopup();
542: }
543: }
544: }
545: }
546:
547: public void windowStateChanged(WindowEvent windowEvent) {
548: if (showingPopup) {
549: int oldState = windowEvent.getOldState();
550: int newState = windowEvent.getNewState();
551:
552: if (((oldState & Frame.ICONIFIED) == 0)
553: && ((newState & Frame.ICONIFIED) == Frame.ICONIFIED)) {
554: hidePopup();
555: // } else if (((oldState & Frame.ICONIFIED) == Frame.ICONIFIED) &&
556: // ((newState & Frame.ICONIFIED) == 0 )) {
557: // //TODO remember we showed before and show again? I guess not worth the efford, not part of spec.
558: }
559: }
560:
561: }
562:
563: public void componentResized(ComponentEvent evt) {
564: if (showingPopup) {
565: resizePopup();
566: }
567: }
568:
569: public void componentMoved(ComponentEvent evt) {
570: if (showingPopup) {
571: resizePopup();
572: }
573: }
574:
575: }
576:
577: private class MListener extends MouseAdapter {
578: public void mouseClicked(java.awt.event.MouseEvent e) {
579: if (e.getButton() != MouseEvent.BUTTON1) {
580: showMenu(e);
581: } else {
582: if (showingPopup) {
583: hidePopup();
584: } else {
585: showPopup();
586: }
587: }
588: }
589:
590: }
591:
592: private void showMenu(MouseEvent e) {
593: JPopupMenu popup = new JPopupMenu();
594: popup.add(new ProgressListAction(NbBundle.getMessage(
595: StatusLineComponent.class,
596: "StatusLineComponent.ShowProcessList")));
597: popup.add(new ViewAction());
598: popup.add(new CancelAction(true));
599: popup.show((Component) e.getSource(), e.getX(), e.getY());
600: }
601:
602: private class CancelAction extends AbstractAction {
603: public CancelAction(boolean text) {
604: if (text) {
605: putValue(Action.NAME, NbBundle.getMessage(
606: StatusLineComponent.class,
607: "StatusLineComponent.Cancel"));
608: } else {
609: Image icon = (Image) UIManager
610: .get("nb.progress.cancel.icon");
611: if (icon == null) {
612: // for custom L&F?
613: icon = Utilities
614: .loadImage("org/netbeans/progress/module/resources/buton.png");
615: }
616: putValue(Action.SMALL_ICON, new ImageIcon(icon));
617: }
618: setEnabled(handle == null ? false : handle.isAllowCancel());
619: }
620:
621: public void actionPerformed(ActionEvent actionEvent) {
622: InternalHandle hndl = handle;
623: if (hndl != null
624: && hndl.getState() == InternalHandle.STATE_RUNNING) {
625: String message = NbBundle.getMessage(
626: StatusLineComponent.class, "Cancel_Question",
627: handle.getDisplayName());
628: String title = NbBundle.getMessage(
629: StatusLineComponent.class,
630: "Cancel_Question_Title");
631: NotifyDescriptor dd = new NotifyDescriptor(message,
632: title, NotifyDescriptor.YES_NO_OPTION,
633: NotifyDescriptor.QUESTION_MESSAGE, null, null);
634: Object retType = DialogDisplayer.getDefault()
635: .notify(dd);
636: if (retType == NotifyDescriptor.YES_OPTION
637: && hndl.getState() == InternalHandle.STATE_RUNNING) {
638: hndl.requestCancel();
639: }
640: }
641: }
642: }
643:
644: private class ViewAction extends AbstractAction {
645: public ViewAction() {
646: putValue(Action.NAME, NbBundle.getMessage(
647: StatusLineComponent.class,
648: "StatusLineComponent.View"));
649: setEnabled(handle == null ? false : handle.isAllowView());
650:
651: }
652:
653: public void actionPerformed(ActionEvent actionEvent) {
654: if (handle != null) {
655: handle.requestView();
656: }
657: }
658: }
659:
660: private class EmptyCancelAction extends AbstractAction {
661: public EmptyCancelAction() {
662: setEnabled(false);
663: putValue(Action.SMALL_ICON, new Icon() {
664: public int getIconHeight() {
665: return 12;
666: }
667:
668: public int getIconWidth() {
669: return 12;
670: }
671:
672: public void paintIcon(Component c, Graphics g, int x,
673: int y) {
674: }
675: });
676: putValue(Action.NAME, "");
677: }
678:
679: public void actionPerformed(ActionEvent e) {
680: }
681: }
682:
683: }
|