View Javadoc

1   /*
2    *  jDTAUS Core Utilities
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.core.monitor.util;
22  
23  import java.awt.Component;
24  import java.awt.Dialog;
25  import java.awt.Frame;
26  import java.awt.GraphicsEnvironment;
27  import java.awt.GridBagConstraints;
28  import java.awt.GridBagLayout;
29  import java.awt.HeadlessException;
30  import java.awt.Insets;
31  import java.awt.Window;
32  import java.awt.event.ActionEvent;
33  import java.awt.event.ActionListener;
34  import java.util.Arrays;
35  import java.util.Calendar;
36  import java.util.Date;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.Locale;
40  import java.util.Map;
41  import java.util.Timer;
42  import java.util.TimerTask;
43  import javax.swing.BorderFactory;
44  import javax.swing.JButton;
45  import javax.swing.JDialog;
46  import javax.swing.JLabel;
47  import javax.swing.JOptionPane;
48  import javax.swing.JPanel;
49  import javax.swing.JProgressBar;
50  import javax.swing.SwingUtilities;
51  import javax.swing.UIManager;
52  import javax.swing.border.TitledBorder;
53  import org.jdtaus.core.container.ContainerFactory;
54  import org.jdtaus.core.logging.spi.Logger;
55  import org.jdtaus.core.monitor.Task;
56  import org.jdtaus.core.monitor.TaskEvent;
57  import org.jdtaus.core.monitor.TaskListener;
58  import org.jdtaus.core.text.Message;
59  
60  /**
61   * {@code TaskListener} displaying progress using a Swing dialog.
62   *
63   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
64   * @version $JDTAUS: SwingProgressMonitor.java 8641 2012-09-27 06:45:17Z schulte $
65   *
66   * @see #onTaskEvent(TaskEvent)
67   */
68  public final class SwingProgressMonitor implements TaskListener
69  {
70      //--Dependencies------------------------------------------------------------
71  
72  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
73      // This section is managed by jdtaus-container-mojo.
74  
75      /**
76       * Gets the configured <code>Logger</code> implementation.
77       *
78       * @return The configured <code>Logger</code> implementation.
79       */
80      private Logger getLogger()
81      {
82          return (Logger) ContainerFactory.getContainer().
83              getDependency( this, "Logger" );
84  
85      }
86  
87      /**
88       * Gets the configured <code>Locale</code> implementation.
89       *
90       * @return The configured <code>Locale</code> implementation.
91       */
92      private Locale getLocale()
93      {
94          return (Locale) ContainerFactory.getContainer().
95              getDependency( this, "Locale" );
96  
97      }
98  
99  // </editor-fold>//GEN-END:jdtausDependencies
100 
101     //------------------------------------------------------------Dependencies--
102     //--Properties--------------------------------------------------------------
103 
104 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
105     // This section is managed by jdtaus-container-mojo.
106 
107     /**
108      * Gets the value of property <code>defaultMinimumTaskDuration</code>.
109      *
110      * @return Default minimum number of milliseconds a task should be shown in the dialog.
111      */
112     private java.lang.Integer getDefaultMinimumTaskDuration()
113     {
114         return (java.lang.Integer) ContainerFactory.getContainer().
115             getProperty( this, "defaultMinimumTaskDuration" );
116 
117     }
118 
119     /**
120      * Gets the value of property <code>defaultMillisToPopup</code>.
121      *
122      * @return Default number of milliseconds visibility of the dialog is delayed.
123      */
124     private java.lang.Integer getDefaultMillisToPopup()
125     {
126         return (java.lang.Integer) ContainerFactory.getContainer().
127             getProperty( this, "defaultMillisToPopup" );
128 
129     }
130 
131     /**
132      * Gets the value of property <code>defaultMillisToDecideToPopup</code>.
133      *
134      * @return Default number of milliseconds to pass before all running tasks are checked for theire duration.
135      */
136     private java.lang.Integer getDefaultMillisToDecideToPopup()
137     {
138         return (java.lang.Integer) ContainerFactory.getContainer().
139             getProperty( this, "defaultMillisToDecideToPopup" );
140 
141     }
142 
143     /**
144      * Gets the value of property <code>defaultColumns</code>.
145      *
146      * @return Default number of columns the preferred width of the progress dialog is computed with.
147      */
148     private java.lang.Integer getDefaultColumns()
149     {
150         return (java.lang.Integer) ContainerFactory.getContainer().
151             getProperty( this, "defaultColumns" );
152 
153     }
154 
155 // </editor-fold>//GEN-END:jdtausProperties
156 
157     //--------------------------------------------------------------Properties--
158     //--TaskListener------------------------------------------------------------
159 
160     /**
161      * {@inheritDoc}
162      * <p>This method controls a dialog displaying a panel for each task showing the progress of that task optionally
163      * providing a cancel button if the corresponding task is cancelable. The dialog will show up only if the operation
164      * performed by at least one task is believed to run longer than specified by property {@code millisToPopup}.
165      * Property {@code millisToDecideToPopup} controls the number of milliseconds to pass before all currently running
166      * tasks are checked for their duration. Properties {@code millisToDecideToPopup} and {@code millisToPopup} are
167      * used in the same way as specified for Swing's {@link javax.swing.ProgressMonitor}. The default for property
168      * {@code millisToDecideToPopup} is 500ms and the default for property {@code millisToPopup} is 2000ms.</p>
169      *
170      * @param event The event send by a {@code Task}.
171      */
172     public void onTaskEvent( final TaskEvent event )
173     {
174         if ( event != null )
175         {
176             switch ( event.getType() )
177             {
178                 case TaskEvent.STARTED:
179                     this.onTaskStarted( event );
180                     break;
181 
182                 case TaskEvent.CHANGED_STATE:
183                     this.onTaskChangedState( event );
184                     break;
185 
186                 case TaskEvent.ENDED:
187                     this.onTaskEnded( event );
188                     break;
189 
190                 default:
191                     getLogger().warn( this.getUnknownTaskEventTypeMessage(
192                         this.getLocale(), new Integer( event.getType() ) ) );
193 
194             }
195         }
196 
197         updateProgressDialog();
198     }
199 
200     //------------------------------------------------------------TaskListener--
201     //--SwingProgressMonitor----------------------------------------------------
202 
203     /** State of a {@code Task} being monitored. */
204     private static final class MonitorState
205     {
206 
207         /** The {@code Task} being monitored. */
208         final Task task;
209 
210         /** The panel to use for displaying progress of {@code task}. */
211         final ProgressPanel panel;
212 
213         /** {@code ActionListener} listening for the cancel button. */
214         ActionListener cancelListener;
215 
216         /** The time the {@code task}'s panel was set visible or negative, if the panel is not visible. */
217         long visibleMillis = Long.MIN_VALUE;
218 
219         /**
220          * Creates a new {@code MonitorState} instance.
221          *
222          * @param task The {@code Task} being monitored.
223          * @param panel The panel to use for displaying progress of {@code task}.
224          * @param cancelListener The listener to listen for the cancel button.
225          */
226         MonitorState( final Task task, final ProgressPanel panel )
227         {
228             super();
229             this.task = task;
230             this.panel = panel;
231         }
232 
233     }
234 
235     /** The current parent component to use when displaying progress. */
236     private Component parent;
237 
238     /** Maps {@code Task} instances to {@code TaskMonitorState} instances. */
239     private final Map tasks = new HashMap( 100 );
240 
241     /** The dialog displaying progress of all {@code Task}s. */
242     private ProgressDialog dialog;
243 
244     /** Number of milliseconds to pass before all running tasks are checked for their duration. */
245     private Integer millisToDecideToPopup;
246 
247     /** Number of milliseconds visibility of the dialog is delayed. */
248     private Integer millisToPopup;
249 
250     /** Minimum number of milliseconds a task is shown. */
251     private Integer minimumTaskDuration;
252 
253     /** Number of columns the preferred width of the progress pane is computed with. */
254     private Integer columns;
255 
256     /**
257      * The time the decision was made to popup a dialog, if an operation of any of the tasks being monitored would take
258      * longer than {@code millisToPopup}.
259      */
260     private long popupDecisionMillis = NO_POPUPDECISION;
261     private static final long NO_POPUPDECISION = Long.MIN_VALUE;
262 
263     /** Timer used to delay removal of panels. */
264     private final Timer timer = new Timer( true );
265 
266     /**
267      * Creates a new {@code SwingProgressMonitor} instance taking the parent component to use when displaying progress.
268      *
269      * @param parent The parent component to use when displaying progress.
270      *
271      * @throws NullPointerException if {@code parent} is {@code null}.
272      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
273      *
274      * @see #onTaskEvent(TaskEvent)
275      */
276     public SwingProgressMonitor( final Component parent )
277     {
278         this( parent, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE );
279     }
280 
281     /**
282      * Creates a new {@code SwingProgressMonitor} instance taking the parent component to use when displaying progress
283      * and configuration for how long to wait before showing the progress dialog.
284      *
285      * @param parent The parent component to use when displaying progress.
286      * @param millisToDecideToPopup The number of milliseconds which have to pass before the duration of any currently
287      * running task is computed.
288      * @param millisToPopup The number of milliseconds at least one task has to run before the progress dialog shows up.
289      *
290      * @throws NullPointerException if {@code parent} is {@code null}.
291      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
292      *
293      * @see #onTaskEvent(TaskEvent)
294      */
295     public SwingProgressMonitor( final Component parent, final int millisToDecideToPopup, final int millisToPopup )
296     {
297         this( parent, millisToDecideToPopup, millisToPopup, Integer.MIN_VALUE, Integer.MIN_VALUE );
298     }
299 
300     /**
301      * Creates a new {@code SwingProgressMonitor} instance taking the parent component to use when displaying progress
302      * and configuration for how long to wait before showing the progress dialog and for how long a task is displayed
303      * minimally.
304      *
305      * @param parent The parent component to use when displaying progress.
306      * @param millisToDecideToPopup The number of milliseconds which have to pass before the duration of any currently
307      * running task is computed.
308      * @param millisToPopup The number of milliseconds at least one task has to run before the progress dialog shows up.
309      * @param minimumTaskDurationMillis The number of milliseconds a task is held visible, if a task is ended right
310      * after having been shown.
311      *
312      * @throws NullPointerException if {@code parent} is {@code null}.
313      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
314      *
315      * @see #onTaskEvent(TaskEvent)
316      * @since 1.10
317      */
318     public SwingProgressMonitor( final Component parent, final int millisToDecideToPopup, final int millisToPopup,
319                                  final int minimumTaskDurationMillis )
320     {
321         this( parent, millisToDecideToPopup, millisToPopup, Integer.MIN_VALUE, Integer.MIN_VALUE );
322     }
323 
324     /**
325      * Creates a new {@code SwingProgressMonitor} instance taking the parent component to use when displaying progress
326      * and configuration for how long to wait before showing the progress dialog, for how long a task is displayed
327      * minimally and a number of columns the preferred width of the progress dialog is computed with.
328      *
329      * @param parent The parent component to use when displaying progress.
330      * @param millisToDecideToPopup The number of milliseconds which have to pass before the duration of any currently
331      * running task is computed.
332      * @param millisToPopup The number of milliseconds at least one task has to run before the progress dialog shows up.
333      * @param minimumTaskDurationMillis The number of milliseconds a task is held visible, if a task is ended right
334      * after having been shown.
335      * @param columns The number of columns the preferred width of the progress dialog is computed with.
336      *
337      * @throws NullPointerException if {@code parent} is {@code null}.
338      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
339      *
340      * @see #onTaskEvent(TaskEvent)
341      * @since 1.10
342      */
343     public SwingProgressMonitor( final Component parent, final int millisToDecideToPopup, final int millisToPopup,
344                                  final int minimumTaskDurationMillis, final int columns )
345     {
346         if ( parent == null )
347         {
348             throw new NullPointerException( "parent" );
349         }
350 
351         if ( GraphicsEnvironment.isHeadless() )
352         {
353             throw new HeadlessException();
354         }
355 
356         if ( millisToDecideToPopup > 0 )
357         {
358             this.millisToDecideToPopup = new Integer( millisToDecideToPopup );
359         }
360         if ( millisToPopup > 0 )
361         {
362             this.millisToPopup = new Integer( millisToPopup );
363         }
364         if ( minimumTaskDurationMillis > 0 )
365         {
366             this.minimumTaskDuration = new Integer( minimumTaskDurationMillis );
367         }
368         if ( columns > 0 )
369         {
370             this.columns = new Integer( columns );
371         }
372 
373         this.parent = parent;
374     }
375 
376     /**
377      * Gets the parent component for any progress displays.
378      *
379      * @return The parent component for any progress displays.
380      */
381     public Component getParent()
382     {
383         return this.parent;
384     }
385 
386     /**
387      * Sets the parent component to be used by any progress displays.
388      *
389      * @param parent The parent component to be used by any progress displays.
390      *
391      * @throws NullPointerException if {@code parent} is {@code null}.
392      */
393     public void setParent( final Component parent )
394     {
395         if ( parent == null )
396         {
397             throw new NullPointerException( "parent" );
398         }
399 
400         this.parent = parent;
401     }
402 
403     /**
404      * Gets the number of milliseconds visibility of the dialog is delayed.
405      *
406      * @return The number of milliseconds visibility of the dialog is delayed.
407      */
408     public int getMillisToPopup()
409     {
410         if ( this.millisToPopup == null )
411         {
412             this.millisToPopup = this.getDefaultMillisToPopup();
413         }
414 
415         return this.millisToPopup.intValue();
416     }
417 
418     /**
419      * Gets the number of milliseconds to pass before all currently running tasks are checked for theire duration.
420      *
421      * @return The number of milliseconds to pass before all currently running tasks are checked for theire duration.
422      */
423     public int getMillisToDecideToPopup()
424     {
425         if ( this.millisToDecideToPopup == null )
426         {
427             this.millisToDecideToPopup = this.getDefaultMillisToDecideToPopup();
428         }
429 
430         return this.millisToDecideToPopup.intValue();
431     }
432 
433     /**
434      * Gets the minimum number of milliseconds to pass before a visible task is set invisible.
435      *
436      * @return The minimum number of milliseconds to pass before a visible task is set invisible.
437      *
438      * @since 1.10
439      */
440     public int getMinimumTaskDuration()
441     {
442         if ( this.minimumTaskDuration == null )
443         {
444             this.minimumTaskDuration = this.getDefaultMinimumTaskDuration();
445         }
446 
447         return this.minimumTaskDuration.intValue();
448     }
449 
450     /**
451      * Gets the number of columns the preferred width of a progress pane is computed with.
452      *
453      * @return The number of columns the preferred width of a progress pane is computed with.
454      *
455      * @since 1.10
456      */
457     public int getColumns()
458     {
459         if ( this.columns == null )
460         {
461             this.columns = this.getDefaultColumns();
462         }
463 
464         return this.columns.intValue();
465     }
466 
467     /**
468      * Gets the dialog displaying progress of all {@code Task}s.
469      *
470      * @return The dialog displaying progress of all {@code Task}s.
471      */
472     private ProgressDialog getDialog()
473     {
474         if ( this.dialog == null )
475         {
476             final Window window = this.getWindowForComponent( this.getParent() );
477 
478             if ( window instanceof Frame )
479             {
480                 this.dialog = new ProgressDialog( (Frame) window );
481             }
482             else if ( window instanceof Dialog )
483             {
484                 this.dialog = new ProgressDialog( (Dialog) window );
485             }
486         }
487 
488         return this.dialog;
489     }
490 
491     /** Closes and disposes the dialog. */
492     private void closeDialog()
493     {
494         if ( this.dialog != null )
495         {
496             this.dialog.setVisible( false );
497             this.dialog.dispose();
498             this.dialog = null;
499         }
500     }
501 
502     /**
503      * Returns the specified component's top-level {@code Frame} or {@code Dialog}.
504      *
505      * @param parentComponent The {@code Component} to check for a {@code Frame} or {@code Dialog}.
506      *
507      * @return the {@code Frame} or {@code Dialog} that contains {@code parentComponent}, or the default frame if the
508      * component is {@code null}, or does not have a valid {@code Frame} or {@code Dialog} parent.
509      *
510      * @throws HeadlessException if {@code GraphicsEnvironment.isHeadless()} returns {@code true}.
511      *
512      * @see java.awt.GraphicsEnvironment#isHeadless
513      */
514     private Window getWindowForComponent( final Component parentComponent ) throws HeadlessException
515     {
516         Window window = JOptionPane.getRootFrame();
517 
518         if ( parentComponent != null )
519         {
520             if ( parentComponent instanceof Frame || parentComponent instanceof Dialog )
521             {
522                 window = (Window) parentComponent;
523             }
524             else
525             {
526                 this.getWindowForComponent( parentComponent.getParent() );
527             }
528         }
529 
530         return window;
531     }
532 
533     /**
534      * Starts monitoring a task.
535      *
536      * @param event The {@code TaskEvent} indicating the start of a {@code Task}.
537      *
538      * @throws NullPointerException if {@code event} is {@code null}.
539      * @throws IllegalArgumentException if {@code event.getType()} is not equal to {@link TaskEvent#STARTED}.
540      */
541     private void onTaskStarted( final TaskEvent event )
542     {
543         if ( event == null )
544         {
545             throw new NullPointerException( "event" );
546         }
547         if ( event.getType() != TaskEvent.STARTED )
548         {
549             throw new IllegalArgumentException( Integer.toString( event.getType() ) );
550         }
551 
552         synchronized ( this.tasks )
553         {
554             final MonitorState state =
555                 new MonitorState( event.getTask(), new ProgressPanel( getColumns() ) );
556 
557             state.cancelListener = new ActionListener()
558             {
559 
560                 public void actionPerformed( final ActionEvent e )
561                 {
562                     if ( !state.task.isCancelled() )
563                     {
564                         state.task.setCancelled( true );
565 
566                         SwingUtilities.invokeLater( new Runnable()
567                         {
568 
569                             public void run()
570                             {
571                                 state.panel.getCancelButton().setText( getTaskCancelledMessage( getLocale() ) );
572                                 state.panel.getCancelButton().setEnabled( false );
573                             }
574 
575                         } );
576                     }
577                 }
578 
579             };
580 
581             if ( this.tasks.put( state.task, state ) != null )
582             {
583                 throw new IllegalStateException( getTaskAlreadyStartedMessage(
584                     getLocale(), state.task.getDescription().getText( getLocale() ),
585                     new Date( state.task.getTimestamp() ) ) );
586 
587             }
588 
589             SwingUtilities.invokeLater( new Runnable()
590             {
591 
592                 public void run()
593                 {
594                     final TitledBorder border =
595                         BorderFactory.createTitledBorder( state.task.getDescription().getText( getLocale() ) );
596 
597                     state.panel.setBorder( border );
598 
599                     if ( state.task.getProgressDescription() != null )
600                     {
601                         state.panel.getProgressDescriptionLabel().setText(
602                             state.task.getProgressDescription().getText( getLocale() ) );
603 
604                     }
605                     else
606                     {
607                         state.panel.getProgressDescriptionLabel().setVisible( false );
608                     }
609 
610                     state.panel.getProgressBar().setIndeterminate( true );
611 
612                     if ( !state.task.isIndeterminate() )
613                     {
614                         state.panel.getProgressBar().setMinimum( state.task.getMinimum() );
615                         state.panel.getProgressBar().setMaximum( state.task.getMaximum() );
616                         state.panel.getProgressBar().setValue( state.task.getProgress() );
617                         state.panel.getTimeLabel().setText( getComputingExpectedDurationMessage( getLocale() ) );
618                     }
619                     else
620                     {
621                         state.panel.getTimeLabel().setText( getIndeterminateDurationMessage( getLocale() ) );
622                     }
623 
624                     state.panel.getCancelButton().setVisible( state.task.isCancelable() );
625                     state.panel.getCancelButton().addActionListener( state.cancelListener );
626                     state.panel.setVisible( false );
627                     getDialog().add( state.panel );
628                 }
629 
630             } );
631         }
632     }
633 
634     /**
635      * Finishes monitoring a task.
636      *
637      * @param event The {@code TaskEvent} indicating the end of a {@code Task}.
638      *
639      * @throws NullPointerException if {@code event} is {@code null}.
640      * @throws IllegalArgumentException if {@code event.getType()} is not equal to {@link TaskEvent#ENDED}.
641      */
642     private void onTaskEnded( final TaskEvent event )
643     {
644         if ( event == null )
645         {
646             throw new NullPointerException( "event" );
647         }
648         if ( event.getType() != TaskEvent.ENDED )
649         {
650             throw new IllegalArgumentException( Integer.toString( event.getType() ) );
651         }
652 
653         final Runnable taskEnded = new Runnable()
654         {
655 
656             public void run()
657             {
658                 synchronized ( tasks )
659                 {
660                     final MonitorState state = (MonitorState) tasks.remove( event.getTask() );
661 
662                     assert state != null : "Expected a started task.";
663 
664                     state.visibleMillis = Long.MIN_VALUE;
665 
666                     SwingUtilities.invokeLater( new Runnable()
667                     {
668 
669                         public void run()
670                         {
671                             state.panel.getCancelButton().removeActionListener( state.cancelListener );
672                             state.panel.setVisible( false );
673                             getDialog().remove( state.panel );
674                             updateProgressDialog(); // Ensure dialog gets closed.
675                         }
676 
677                     } );
678                 }
679             }
680 
681         };
682 
683         synchronized ( tasks )
684         {
685             final MonitorState state = (MonitorState) tasks.get( event.getTask() );
686 
687             assert state != null : "Expected a started task.";
688 
689             SwingUtilities.invokeLater( new Runnable()
690             {
691 
692                 public void run()
693                 {
694                     state.panel.getTimeLabel().setText( getTaskCompletedMessage( getLocale(), new Date() ) );
695                 }
696 
697             } );
698 
699             if ( state.visibleMillis > 0L
700                  && System.currentTimeMillis() - state.visibleMillis < getMinimumTaskDuration() )
701             {
702                 try
703                 {
704                     timer.schedule( new TimerTask()
705                     {
706 
707                         public void run()
708                         {
709                             taskEnded.run();
710                         }
711 
712                     }, getMinimumTaskDuration() );
713                 }
714                 catch ( final IllegalStateException e )
715                 {
716                     getLogger().error( e );
717                     taskEnded.run();
718                 }
719             }
720             else
721             {
722                 taskEnded.run();
723             }
724         }
725     }
726 
727     /**
728      * Monitors a {@code Task}'s state.
729      *
730      * @param event The {@code TaskEvent} indicating a state change of a {@code Task}.
731      *
732      * @throws NullPointerException if {@code event} is {@code null}.
733      * @throws IllegalArgumentException if {@code event.getType()} is not equal to {@link TaskEvent#CHANGED_STATE}.
734      */
735     private void onTaskChangedState( final TaskEvent event )
736     {
737         if ( event == null )
738         {
739             throw new NullPointerException( "event" );
740         }
741         if ( event.getType() != TaskEvent.CHANGED_STATE )
742         {
743             throw new IllegalArgumentException( Integer.toString( event.getType() ) );
744         }
745 
746         synchronized ( tasks )
747         {
748             final MonitorState state = (MonitorState) tasks.get( event.getTask() );
749 
750             assert state != null : "Expected a started task.";
751 
752             final int progress;
753             final boolean indeterminate;
754             final Message progressDescription = state.task.getProgressDescription();
755 
756             if ( !state.task.isIndeterminate() )
757             {
758                 progress = state.task.getProgress();
759                 indeterminate = false;
760             }
761             else
762             {
763                 progress = Integer.MIN_VALUE;
764                 indeterminate = true;
765             }
766 
767             SwingUtilities.invokeLater( new Runnable()
768             {
769 
770                 public void run()
771                 {
772                     if ( !indeterminate )
773                     {
774                         state.panel.getProgressBar().setValue( progress );
775                     }
776 
777                     if ( progressDescription != null )
778                     {
779                         final String oldText = state.panel.getProgressDescriptionLabel().getText();
780                         final String newText = progressDescription.getText( getLocale() );
781 
782                         if ( oldText == null || !oldText.equals( newText ) )
783                         {
784                             state.panel.getProgressDescriptionLabel().setText( newText );
785 
786                             if ( !state.panel.getProgressDescriptionLabel().isVisible() )
787                             {
788                                 state.panel.getProgressDescriptionLabel().setVisible( true );
789                             }
790                         }
791                     }
792                     else if ( state.panel.getProgressDescriptionLabel().isVisible() )
793                     {
794                         state.panel.getProgressDescriptionLabel().setVisible( false );
795                     }
796                 }
797 
798             } );
799         }
800     }
801 
802     /** Updates the {@code ProgressDialog}. */
803     private void updateProgressDialog()
804     {
805         synchronized ( this.tasks )
806         {
807             if ( this.tasks.size() > 0 )
808             {
809                 final long now = System.currentTimeMillis();
810 
811                 if ( this.popupDecisionMillis == NO_POPUPDECISION )
812                 {
813                     this.popupDecisionMillis = now;
814                 }
815                 else if ( now - this.popupDecisionMillis > this.getMillisToDecideToPopup() )
816                 {
817                     // If any task's operation runs longer than millisToPopup, show the dialog.
818                     for ( Iterator it = this.tasks.entrySet().iterator(); it.hasNext(); )
819                     {
820                         final Map.Entry entry = (Map.Entry) it.next();
821                         final MonitorState state = (MonitorState) entry.getValue();
822 
823                         if ( !state.task.isIndeterminate() )
824                         {
825                             final long progressed = state.task.getProgress() - state.task.getMinimum();
826                             final long predicted = ( now - state.task.getTimestamp() )
827                                                    * ( state.task.getMaximum() - state.task.getMinimum() )
828                                                    / ( progressed == 0L ? 1L : progressed );
829 
830                             final Calendar cal = Calendar.getInstance();
831                             cal.setTimeInMillis( state.task.getTimestamp() + predicted );
832 
833                             if ( progressed > 0L )
834                             {
835                                 SwingUtilities.invokeLater( new Runnable()
836                                 {
837 
838                                     public void run()
839                                     {
840                                         state.panel.getTimeLabel().setText( getExpectedEndMessage(
841                                             getLocale(), cal.getTime() ) );
842 
843                                         if ( state.panel.getProgressBar().isIndeterminate() )
844                                         {
845                                             state.panel.getProgressBar().setIndeterminate( false );
846                                         }
847                                     }
848 
849                                 } );
850                             }
851                         }
852 
853                         if ( state.visibleMillis < 0L && now - state.task.getTimestamp() > this.getMillisToPopup() )
854                         {
855                             state.visibleMillis = System.currentTimeMillis();
856 
857                             SwingUtilities.invokeLater( new Runnable()
858                             {
859 
860                                 public void run()
861                                 {
862                                     state.panel.setVisible( true );
863                                     getDialog().pack();
864 
865                                     if ( !getDialog().isVisible() )
866                                     {
867                                         getDialog().setLocationRelativeTo( getParent() );
868                                         getDialog().setVisible( true );
869                                     }
870                                 }
871 
872                             } );
873                         }
874                     }
875                 }
876             }
877             else
878             {
879                 SwingUtilities.invokeLater( new Runnable()
880                 {
881 
882                     public void run()
883                     {
884                         closeDialog();
885                     }
886 
887                 } );
888 
889                 this.popupDecisionMillis = NO_POPUPDECISION;
890             }
891         }
892     }
893 
894     /** Finalizes the instance by canceling timers. */
895     protected void finalize() throws Throwable
896     {
897         this.timer.cancel();
898         super.finalize();
899     }
900 
901     //----------------------------------------------------SwingProgressMonitor--
902     //--Messages----------------------------------------------------------------
903 
904 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
905     // This section is managed by jdtaus-container-mojo.
906 
907     /**
908      * Gets the text of message <code>unknownTaskEventType</code>.
909      * <blockquote><pre>Verarbeitungsereignis unbekannten Typs {0,number} ignoriert.</pre></blockquote>
910      * <blockquote><pre>Ignored task event of unknown type {0,number}.</pre></blockquote>
911      *
912      * @param locale The locale of the message instance to return.
913      * @param unknownEventType The unknown type of the event.
914      *
915      * @return Message stating that an unknown task event got ignored.
916      */
917     private String getUnknownTaskEventTypeMessage( final Locale locale,
918             final java.lang.Number unknownEventType )
919     {
920         return ContainerFactory.getContainer().
921             getMessage( this, "unknownTaskEventType", locale,
922                 new Object[]
923                 {
924                     unknownEventType
925                 });
926 
927     }
928 
929     /**
930      * Gets the text of message <code>taskAlreadyStarted</code>.
931      * <blockquote><pre>Verarbeitung "{0}" wurde um {1,time,long} bereits gestartet.</pre></blockquote>
932      * <blockquote><pre>Task "{0}" has already been started at {1,time,long}.</pre></blockquote>
933      *
934      * @param locale The locale of the message instance to return.
935      * @param taskDescription The description of the task.
936      * @param startDate The time the task started.
937      *
938      * @return Message stating that a task already has been started.
939      */
940     private String getTaskAlreadyStartedMessage( final Locale locale,
941             final java.lang.String taskDescription,
942             final java.util.Date startDate )
943     {
944         return ContainerFactory.getContainer().
945             getMessage( this, "taskAlreadyStarted", locale,
946                 new Object[]
947                 {
948                     taskDescription,
949                     startDate
950                 });
951 
952     }
953 
954     /**
955      * Gets the text of message <code>expectedEnd</code>.
956      * <blockquote><pre>Voraussichtliches Ende am {0,date,full} um {0,time,medium} Uhr.</pre></blockquote>
957      * <blockquote><pre>Expected end approximately at {0,date,full} {0,time,medium}.</pre></blockquote>
958      *
959      * @param locale The locale of the message instance to return.
960      * @param expectedEnd The expected end of the task.
961      *
962      * @return Message stating the expected end of a task.
963      */
964     private String getExpectedEndMessage( final Locale locale,
965             final java.util.Date expectedEnd )
966     {
967         return ContainerFactory.getContainer().
968             getMessage( this, "expectedEnd", locale,
969                 new Object[]
970                 {
971                     expectedEnd
972                 });
973 
974     }
975 
976     /**
977      * Gets the text of message <code>computingExpectedDuration</code>.
978      * <blockquote><pre>Berechnet voraussichtliche Dauer...</pre></blockquote>
979      * <blockquote><pre>Computing expected duration...</pre></blockquote>
980      *
981      * @param locale The locale of the message instance to return.
982      *
983      * @return Message stating the expected duration of a task is being computed.
984      */
985     private String getComputingExpectedDurationMessage( final Locale locale )
986     {
987         return ContainerFactory.getContainer().
988             getMessage( this, "computingExpectedDuration", locale, null );
989 
990     }
991 
992     /**
993      * Gets the text of message <code>indeterminateDuration</code>.
994      * <blockquote><pre>Keine Laufzeitinformationen verfügbar.</pre></blockquote>
995      * <blockquote><pre>No duration information available.</pre></blockquote>
996      *
997      * @param locale The locale of the message instance to return.
998      *
999      * @return Message stating the a task is indeterminate.
1000      */
1001     private String getIndeterminateDurationMessage( final Locale locale )
1002     {
1003         return ContainerFactory.getContainer().
1004             getMessage( this, "indeterminateDuration", locale, null );
1005 
1006     }
1007 
1008     /**
1009      * Gets the text of message <code>taskCompleted</code>.
1010      * <blockquote><pre>Vorgang am {0,date,full} um {0,time,medium} Uhr abgeschlossen.</pre></blockquote>
1011      * <blockquote><pre>Task completed at {0,date,full} {0,time,medium}.</pre></blockquote>
1012      *
1013      * @param locale The locale of the message instance to return.
1014      * @param completedDate The date the task completed.
1015      *
1016      * @return Message stating that a task completed.
1017      */
1018     private String getTaskCompletedMessage( final Locale locale,
1019             final java.util.Date completedDate )
1020     {
1021         return ContainerFactory.getContainer().
1022             getMessage( this, "taskCompleted", locale,
1023                 new Object[]
1024                 {
1025                     completedDate
1026                 });
1027 
1028     }
1029 
1030     /**
1031      * Gets the text of message <code>taskCancelled</code>.
1032      * <blockquote><pre>Abgebrochen</pre></blockquote>
1033      * <blockquote><pre>Cancelled</pre></blockquote>
1034      *
1035      * @param locale The locale of the message instance to return.
1036      *
1037      * @return Message stating that a task is cancelled.
1038      */
1039     private String getTaskCancelledMessage( final Locale locale )
1040     {
1041         return ContainerFactory.getContainer().
1042             getMessage( this, "taskCancelled", locale, null );
1043 
1044     }
1045 
1046 // </editor-fold>//GEN-END:jdtausMessages
1047 
1048     //----------------------------------------------------------------Messages--
1049 }
1050 
1051 /**
1052  * {@code JPanel} displaying the progress for a {@code Task}.
1053  *
1054  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
1055  * @version $JDTAUS: SwingProgressMonitor.java 8641 2012-09-27 06:45:17Z schulte $
1056  */
1057 class ProgressPanel extends JPanel
1058 {
1059     //--ProgressPanel-----------------------------------------------------------
1060 
1061     /** Serial version UID for backwards compatibility with 1.1.x classes. */
1062     private static final long serialVersionUID = 7471966908616369847L;
1063 
1064     /** {@code JLabel} for displaying a {@code Task}s progress description. */
1065     private final JLabel progressDescriptionLabel;
1066 
1067     /** {@code JLabel} for displaying the time to run. */
1068     private final JLabel timeLabel;
1069 
1070     /** {@code JProgressBar} for displaying a {@code Task}s progress. */
1071     private final JProgressBar progressBar;
1072 
1073     /** {@code JButton} for canceling a {@code Task}. */
1074     private final JButton cancelButton;
1075 
1076     /** Creates a new {@code ProgressPanel} instance. */
1077     ProgressPanel( final int columns )
1078     {
1079         final char[] chars = new char[ columns ];
1080         Arrays.fill( chars, 'W' );
1081 
1082         this.timeLabel = new JLabel();
1083         this.timeLabel.setFont( this.timeLabel.getFont().deriveFont( this.timeLabel.getFont().getSize2D() - 2.0F ) );
1084         this.timeLabel.setText( String.valueOf( chars ) );
1085         // Use the peer's preferred size for columns times 'W' char.
1086         this.timeLabel.setPreferredSize( this.timeLabel.getPreferredSize() );
1087         this.timeLabel.setText( null );
1088         this.progressDescriptionLabel = new JLabel();
1089 
1090         this.progressBar = new JProgressBar();
1091         this.cancelButton = new JButton();
1092         this.cancelButton.setText( UIManager.getString( "OptionPane.cancelButtonText" ) );
1093         this.setLayout( new GridBagLayout() );
1094 
1095         GridBagConstraints c = new GridBagConstraints();
1096         c.gridwidth = GridBagConstraints.REMAINDER;
1097         c.anchor = GridBagConstraints.NORTHWEST;
1098         c.weightx = 1.0D;
1099         c.fill = GridBagConstraints.HORIZONTAL;
1100         c.insets = new Insets( 10, 25, 10, 25 );
1101         this.add( this.getProgressBar(), c );
1102 
1103         c = new GridBagConstraints();
1104         c.gridwidth = GridBagConstraints.REMAINDER;
1105         c.anchor = GridBagConstraints.NORTHWEST;
1106         c.weightx = 1.0D;
1107         c.fill = GridBagConstraints.HORIZONTAL;
1108         c.insets = new Insets( 0, 25, 10, 25 );
1109         this.add( this.getProgressDescriptionLabel(), c );
1110 
1111         c = new GridBagConstraints();
1112         c.gridwidth = GridBagConstraints.REMAINDER;
1113         c.anchor = GridBagConstraints.SOUTHEAST;
1114         c.weightx = 1.0D;
1115         c.insets = new Insets( 10, 25, 10, 25 );
1116         this.add( this.getCancelButton(), c );
1117 
1118         c = new GridBagConstraints();
1119         c.gridwidth = GridBagConstraints.REMAINDER;
1120         c.weightx = 1.0D;
1121         c.weighty = 1.0D;
1122         c.anchor = GridBagConstraints.SOUTHWEST;
1123         c.insets = new Insets( 10, 25, 0, 25 );
1124         c.fill = GridBagConstraints.HORIZONTAL;
1125         this.add( this.getTimeLabel(), c );
1126     }
1127 
1128     JLabel getProgressDescriptionLabel()
1129     {
1130         return this.progressDescriptionLabel;
1131     }
1132 
1133     JLabel getTimeLabel()
1134     {
1135         return this.timeLabel;
1136     }
1137 
1138     JProgressBar getProgressBar()
1139     {
1140         return this.progressBar;
1141     }
1142 
1143     JButton getCancelButton()
1144     {
1145         return this.cancelButton;
1146     }
1147 
1148     //-----------------------------------------------------------ProgressPanel--
1149 }
1150 
1151 /**
1152  * {@code JDialog} displaying the progress of all {@code Task}s.
1153  *
1154  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
1155  * @version $JDTAUS: SwingProgressMonitor.java 8641 2012-09-27 06:45:17Z schulte $
1156  */
1157 class ProgressDialog extends JDialog
1158 {
1159     //--ProgressDialog----------------------------------------------------------
1160 
1161     /** Serial version UID for backwards compatibility with 1.1.x classes. */
1162     private static final long serialVersionUID = -8959350486356163001L;
1163 
1164     /**
1165      * Creates a new {@code ProgressDialog} taking the owning frame.
1166      *
1167      * @param owner the frame owning the dialog.
1168      */
1169     ProgressDialog( final Frame owner )
1170     {
1171         super( owner, true );
1172         this.initializeDialog();
1173     }
1174 
1175     /**
1176      * Creates a new {@code ProgressDialog} taking the owning dialog.
1177      *
1178      * @param owner the dialog owning the dialog.
1179      */
1180     ProgressDialog( final Dialog owner )
1181     {
1182         super( owner, true );
1183         this.initializeDialog();
1184     }
1185 
1186     /** Sets the layout and title of the dialog. */
1187     private void initializeDialog()
1188     {
1189         this.getContentPane().setLayout( new GridBagLayout() );
1190         this.setDefaultCloseOperation( DO_NOTHING_ON_CLOSE );
1191         this.setTitle( UIManager.getString( "ProgressMonitor.progressText" ) );
1192     }
1193 
1194     void add( final ProgressPanel panel )
1195     {
1196         if ( panel == null )
1197         {
1198             throw new NullPointerException( "panel" );
1199         }
1200 
1201         final GridBagConstraints c = new GridBagConstraints();
1202         c.gridwidth = GridBagConstraints.REMAINDER;
1203         c.anchor = GridBagConstraints.NORTHWEST;
1204         c.weightx = 1.0D;
1205         c.weighty = 1.0D;
1206         c.fill = GridBagConstraints.BOTH;
1207 
1208         synchronized ( this.getTreeLock() )
1209         {
1210             this.getContentPane().add( panel, c );
1211         }
1212     }
1213 
1214     void remove( final ProgressPanel panel )
1215     {
1216         if ( panel == null )
1217         {
1218             throw new NullPointerException( "panel" );
1219         }
1220 
1221         synchronized ( this.getTreeLock() )
1222         {
1223             this.getContentPane().remove( panel );
1224             this.validate();
1225         }
1226     }
1227 
1228     //----------------------------------------------------------ProgressDialog--
1229 }