/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.gui.commonswing;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.MessageFormat;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import org.pentaho.reporting.engine.classic.core.event.ReportProgressEvent;
import org.pentaho.reporting.engine.classic.core.event.ReportProgressListener;
import org.pentaho.reporting.libraries.base.util.Messages;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.designtime.swing.LibSwingUtil;
/**
* A progress monitor dialog component that visualizes the report processing progress. It will receive update events
* from the report processors and updates the UI according to the latest event data.
* <p/>
* The progress will be computed according to the currently processed table row. This approach provides relativly
* accurate data, but assumes that processing all bands consumes roughly the same time.
*
* @author Thomas Morgner
*/
public class ReportProgressDialog extends JDialog implements ReportProgressListener {
/**
* Handles the update event processing as a separate thread
*
* @author Thomas Morgner
*/
private class ScreenUpdateRunnable implements Runnable {
/**
* The event upon which this update event processing will occur
*/
private ReportProgressEvent event;
/**
* Initializes the update event processing thread with the event information
*/
protected ScreenUpdateRunnable() {
}
/**
* Performs the process of updating all the pieces of the progress dialog with the update event information.
*/
public synchronized void run() {
if ( event == null ) {
return;
}
updatePageMessage( event.getPage() );
updateRowsMessage( event.getRow(), event.getMaximumRow() );
updateActivityMessage( event.getActivity() );
updateProgressBar( event );
this.event = null;
}
public synchronized boolean update( final ReportProgressEvent event ) {
final boolean retval = ( this.event == null );
this.event = event;
return retval;
}
}
/**
* Event handles that will ensure this dialog will remain on top
*
* @author Thomas Morgner
*/
private static class ToFrontHandler extends WindowAdapter {
protected ToFrontHandler() {
}
/**
* Invoked when a window has been opened.
*/
public void windowOpened( final WindowEvent e ) {
e.getWindow().toFront();
}
}
/**
* A label that carries the global message that describes the current task.
*/
private JLabel messageCarrier;
/**
* A label containing the report processing pass count.
*/
private JLabel passCountMessage;
/**
* A label containing the current page.
*/
private JLabel pageCountMessage;
/**
* A label containing the currently processed row.
*/
private JLabel rowCountMessage;
/**
* The progress bar that is used to visualize the progress.
*/
private JProgressBar progressBar;
/**
* The reusable message format for the page label.
*/
private MessageFormat pageMessageFormatter;
/**
* The reusable message format for the rows label.
*/
private MessageFormat rowsMessageFormatter;
/**
* The reusable message format for the pass label.
*/
private MessageFormat passMessageFormatter;
/**
* The last page received.
*/
private int lastPage;
/**
* The last pass values received.
*/
private int lastActivity;
/**
* The last max-row received.
*/
private int lastMaxRow;
/**
* the cached value for the max-row value as integer.
*/
private Integer lastMaxRowInteger; // this values doesnt change much, so reduce GC work
/**
* a text which describes the layouting process.
*/
private String layoutText;
/**
* a text that describes the export phase of the report processing.
*/
private String outputText;
/**
* Localized messages.
*/
private Messages messages;
private boolean onlyPagination;
private ScreenUpdateRunnable updateRunnable;
/**
* Creates a non-modal dialog without a title and with the specified Dialog owner.
*
* @param dialog
* the owner of the dialog
*/
public ReportProgressDialog( final Dialog dialog ) {
super( dialog );
setLocale( dialog.getLocale() );
initConstructor();
}
/**
* Creates a non-modal dialog without a title and with the specified Frame owner.
*
* @param frame
* the owner of the dialog
*/
public ReportProgressDialog( final Frame frame ) {
super( frame );
setLocale( frame.getLocale() );
initConstructor();
}
/**
* Creates a non-modal dialog without a title and without a specified Frame owner. A shared, hidden frame will be set
* as the owner of the Dialog.
*/
public ReportProgressDialog() {
initConstructor();
}
public boolean isOnlyPagination() {
return onlyPagination;
}
public void setOnlyPagination( final boolean onlyPagination ) {
this.onlyPagination = onlyPagination;
}
/**
* Initializes the dialog (Non-GUI stuff).
*/
private void initConstructor() {
updateRunnable = new ScreenUpdateRunnable();
messages =
new Messages( getLocale(), SwingCommonModule.BUNDLE_NAME, ObjectUtilities
.getClassLoader( SwingCommonModule.class ) );
initialize();
addWindowListener( new ToFrontHandler() );
setOutputText( messages.getString( "progress-dialog.perform-output" ) ); //$NON-NLS-1$
setLayoutText( messages.getString( "progress-dialog.prepare-layout" ) ); //$NON-NLS-1$
lastActivity = -1;
lastMaxRow = -1;
lastPage = -1;
pack();
LibSwingUtil.centerDialogInParent( this );
}
/**
* Initializes the GUI components of this dialog.
*/
private void initialize() {
final JPanel contentPane = new JPanel();
contentPane.setBorder( new EmptyBorder( 5, 5, 5, 5 ) );
contentPane.setLayout( new GridBagLayout() );
pageMessageFormatter = new MessageFormat( messages.getString( "progress-dialog.page-label" ) ); //$NON-NLS-1$
rowsMessageFormatter = new MessageFormat( messages.getString( "progress-dialog.rows-label" ) ); //$NON-NLS-1$
passMessageFormatter = new MessageFormat( messages.getString( "progress-dialog.pass-label-0" ) ); //$NON-NLS-1$
messageCarrier = new JLabel( " " ); //$NON-NLS-1$
passCountMessage = new JLabel( " " ); //$NON-NLS-1$
rowCountMessage = new JLabel( " " ); //$NON-NLS-1$
pageCountMessage = new JLabel( " " ); //$NON-NLS-1$
progressBar = new JProgressBar( SwingConstants.HORIZONTAL, 0, 100 );
progressBar.setStringPainted( true );
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets( 3, 1, 5, 1 );
gbc.ipadx = 200;
contentPane.add( messageCarrier, gbc );
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.SOUTHWEST;
gbc.insets = new Insets( 3, 1, 1, 1 );
contentPane.add( passCountMessage, gbc );
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
gbc.insets = new Insets( 3, 1, 1, 1 );
contentPane.add( progressBar, gbc );
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = 1;
gbc.weighty = 1;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.insets = new Insets( 3, 1, 1, 1 );
contentPane.add( pageCountMessage, gbc );
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 3;
gbc.gridwidth = 1;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.insets = new Insets( 3, 10, 1, 1 );
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
contentPane.add( rowCountMessage, gbc );
setContentPane( contentPane );
}
/**
* Returns the current message.
*
* @return the current global message.
*/
public String getMessage() {
return messageCarrier.getText();
}
/**
* Defines the current message.
*
* @param message
* the current global message.
*/
public void setMessage( final String message ) {
messageCarrier.setText( message );
}
/**
* Updates the page message label if the current page has changed.
*
* @param page
* the new page parameter.
*/
protected void updatePageMessage( final int page ) {
if ( lastPage != page ) {
final Object[] parameters = new Object[] { new Integer( page ) };
pageCountMessage.setText( pageMessageFormatter.format( parameters ) );
lastPage = page;
}
}
/**
* Updates the rows message label if either the rows or maxrows changed.
*
* @param rows
* the currently processed rows.
* @param maxRows
* the maximum number of rows in the report.
*/
protected void updateRowsMessage( final int rows, final int maxRows ) {
if ( maxRows != lastMaxRow ) {
lastMaxRowInteger = new Integer( maxRows );
lastMaxRow = maxRows;
}
final Object[] parameters = new Object[] { new Integer( rows ), lastMaxRowInteger };
rowCountMessage.setText( rowsMessageFormatter.format( parameters ) );
}
/**
* Updates the pass message label if either the pass or prepare state changed. The pass reflects the current
* processing level, one level for every function dependency level.
*
* @param activity
* the current reporting pass.
*/
protected void updateActivityMessage( final int activity ) {
if ( lastActivity != activity ) {
lastActivity = activity;
final Object[] parameters = new Object[] { new Integer( activity ) };
passCountMessage.setText( passMessageFormatter.format( parameters ) );
}
}
/**
* Updates the progress bar to show the current progress
*
* @param event
* the event data used to update the progress bar
*/
protected void updateProgressBar( final ReportProgressEvent event ) {
progressBar.setValue( (int) ReportProgressEvent.computePercentageComplete( event, isOnlyPagination() ) );
}
/**
* Returns the current pass message component.
*
* @return the pass message component.
*/
protected final JLabel getPassCountMessage() {
return passCountMessage;
}
/**
* Returns the current pagecount message component.
*
* @return the page message component.
*/
protected final JLabel getPageCountMessage() {
return pageCountMessage;
}
/**
* Returns the current row message component.
*
* @return the row message component.
*/
protected final JLabel getRowCountMessage() {
return rowCountMessage;
}
/**
* Returns the current pass message component.
*
* @return the pass message component.
*/
protected final MessageFormat getPageMessageFormatter() {
return pageMessageFormatter;
}
/**
* Returns the current pass message component.
*
* @return the pass message component.
*/
protected final MessageFormat getRowsMessageFormatter() {
return rowsMessageFormatter;
}
/**
* Returns the current pass message component.
*
* @return the pass message component.
*/
protected final MessageFormat getPassMessageFormatter() {
return passMessageFormatter;
}
/**
* Returns the output text message. This text describes the export phases of the report processing.
*
* @return the output phase description.
*/
public String getOutputText() {
return outputText;
}
/**
* Defines the output text message. This text describes the export phases of the report processing.
*
* @param outputText
* the output message.
*/
public void setOutputText( final String outputText ) {
if ( outputText == null ) {
throw new NullPointerException( messages.getErrorString( "ReportProgressDialog.ERROR_0001_OUTPUT_TEXT_NULL" ) ); //$NON-NLS-1$
}
this.outputText = outputText;
}
/**
* Returns the layout text. This text describes the prepare phases of the report processing.
*
* @return the layout text.
*/
public String getLayoutText() {
return layoutText;
}
/**
* Defines the layout text message. This text describes the prepare phases of the report processing.
*
* @param layoutText
* the layout message.
*/
public void setLayoutText( final String layoutText ) {
if ( layoutText == null ) {
throw new NullPointerException( messages.getErrorString( "ReportProgressDialog.ERROR_0002_LAYOUT_TEXT_NULL" ) ); //$NON-NLS-1$
}
this.layoutText = layoutText;
}
protected boolean isSameMaxRow( final int row ) {
return lastMaxRow == row;
}
public void reportProcessingStarted( final ReportProgressEvent event ) {
postUpdate( event );
}
public void reportProcessingUpdate( final ReportProgressEvent event ) {
postUpdate( event );
}
public void reportProcessingFinished( final ReportProgressEvent event ) {
postUpdate( event );
}
private void postUpdate( final ReportProgressEvent event ) {
synchronized ( this.updateRunnable ) {
if ( this.updateRunnable.update( event ) ) {
if ( SwingUtilities.isEventDispatchThread() ) {
this.updateRunnable.run();
} else {
SwingUtilities.invokeLater( this.updateRunnable );
}
}
}
}
public void setVisibleInEDT( final boolean b ) {
if ( SwingUtilities.isEventDispatchThread() ) {
if ( b ) {
setVisible( true );
} else {
setVisible( false );
}
} else {
try {
SwingUtilities.invokeAndWait( new ShowHideTask( this, b ) );
} catch ( Exception e ) {
// something has to be done!
if ( b ) {
setVisible( true );
} else {
setVisible( false );
}
}
}
}
private static class ShowHideTask implements Runnable {
private Dialog parent;
private boolean visible;
private ShowHideTask( final Dialog parent, final boolean visible ) {
this.parent = parent;
this.visible = visible;
}
/**
* When an object implementing interface <code>Runnable</code> is used to create a thread, starting the thread
* causes the object's <code>run</code> method to be called in that separately executing thread.
* <p/>
* The general contract of the method <code>run</code> is that it may take any action whatsoever.
*
* @see Thread#run()
*/
public void run() {
if ( visible ) {
parent.setVisible( true );
} else {
parent.setVisible( false );
}
}
}
}