001/*
002 *  jDTAUS Core Utilities
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.core.monitor.util;
022
023import java.awt.Component;
024import java.awt.Dialog;
025import java.awt.Frame;
026import java.awt.GraphicsEnvironment;
027import java.awt.GridBagConstraints;
028import java.awt.GridBagLayout;
029import java.awt.HeadlessException;
030import java.awt.Insets;
031import java.awt.Window;
032import java.awt.event.ActionEvent;
033import java.awt.event.ActionListener;
034import java.util.Arrays;
035import java.util.Calendar;
036import java.util.Date;
037import java.util.HashMap;
038import java.util.Iterator;
039import java.util.Locale;
040import java.util.Map;
041import java.util.Timer;
042import java.util.TimerTask;
043import javax.swing.BorderFactory;
044import javax.swing.JButton;
045import javax.swing.JDialog;
046import javax.swing.JLabel;
047import javax.swing.JOptionPane;
048import javax.swing.JPanel;
049import javax.swing.JProgressBar;
050import javax.swing.SwingUtilities;
051import javax.swing.UIManager;
052import javax.swing.border.TitledBorder;
053import org.jdtaus.core.container.ContainerFactory;
054import org.jdtaus.core.logging.spi.Logger;
055import org.jdtaus.core.monitor.Task;
056import org.jdtaus.core.monitor.TaskEvent;
057import org.jdtaus.core.monitor.TaskListener;
058import org.jdtaus.core.text.Message;
059
060/**
061 * {@code TaskListener} displaying progress using a Swing dialog.
062 *
063 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
064 * @version $JDTAUS: SwingProgressMonitor.java 8641 2012-09-27 06:45:17Z schulte $
065 *
066 * @see #onTaskEvent(TaskEvent)
067 */
068public final class SwingProgressMonitor implements TaskListener
069{
070    //--Dependencies------------------------------------------------------------
071
072// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
073    // This section is managed by jdtaus-container-mojo.
074
075    /**
076     * Gets the configured <code>Logger</code> implementation.
077     *
078     * @return The configured <code>Logger</code> implementation.
079     */
080    private Logger getLogger()
081    {
082        return (Logger) ContainerFactory.getContainer().
083            getDependency( this, "Logger" );
084
085    }
086
087    /**
088     * Gets the configured <code>Locale</code> implementation.
089     *
090     * @return The configured <code>Locale</code> implementation.
091     */
092    private Locale getLocale()
093    {
094        return (Locale) ContainerFactory.getContainer().
095            getDependency( this, "Locale" );
096
097    }
098
099// </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 */
1057class 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 */
1157class 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}