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}