All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepsoft.util.swing.MultiProgressComponent Maven / Gradle / Ivy

The newest version!
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * ProgressComponent.java
 *
 * Created on Apr 19, 2012, 7:02:06 PM
 */
package org.pepsoft.util.swing;

import org.pepsoft.util.FileUtils;
import org.pepsoft.util.ProgressReceiver;
import org.pepsoft.util.SubProgressReceiver;
import org.pepsoft.util.swing.ProgressComponent.Listener;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static org.pepsoft.util.AwtUtils.doOnEventThreadAndWait;
import static org.pepsoft.util.ExceptionUtils.chainContains;
import static org.pepsoft.util.mdc.MDCUtils.decorateWithMdcContext;

/**
 * A component which can execute a task in the background, reporting its
 * progress on a progress bar, displaying status messages from the task, and
 * optionally allowing the user to cancel the task.
 *
 * @author pepijn
 */
@SuppressWarnings({"Convert2Lambda", "Anonymous2MethodRef", "unused"}) // Managed by NetBeans
public class MultiProgressComponent extends javax.swing.JPanel implements ProgressReceiver, ActionListener {
    /**
     * Creates a new ProgressComponent
     */
    public MultiProgressComponent() {
        initComponents();

        // Size the scroll pane so that it can show eight progress viewers
        Dimension panelPrefdSize = scrollablePanel1.getPreferredSize();
        ProgressViewer testProgressViewer = new ProgressViewer();
        scrollablePanel1.add(testProgressViewer);
        panelPrefdSize.setSize(panelPrefdSize.getWidth(), testProgressViewer.getPreferredSize().getHeight() * 8);
        scrollablePanel1.remove(testProgressViewer);
        jScrollPane1.setMinimumSize(panelPrefdSize);

        scrollablePanel1.setTrackViewportWidth(true);
        scrollablePanel1.setTrackViewportHeight(false);
    }

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    public Listener getListener() {
        return listener;
    }

    public void setTask(ProgressTask task) {
        this.task = task;
    }

    public ProgressTask getTask() {
        return task;
    }
    
    public void setCancelable(boolean cancelable) {
        this.cancelable = cancelable;
    }
    
    public boolean getCancelable() {
        return cancelable;
    }

    public void start() {
        if ("true".equalsIgnoreCase(System.getProperty("org.pepsoft.worldpainter.devMode"))) {
            stats = new ArrayList<>();
        }
        jButton1.setEnabled(cancelable);
        jProgressBar1.setIndeterminate(true);
        final Thread thread = new Thread(task.getName()) {
            @Override
            public void run() {
                try {
                    result = task.execute(MultiProgressComponent.this);
                    done();
                } catch (Throwable t) {
                    exceptionThrown(t);
                }
            }
        };
        thread.start();
        start = System.currentTimeMillis();
        timer = new Timer(1000, this);
        timer.start();
    }

    /**
     * Add a {@link JButton} to the panel, to the left of the Cancel button.
     *
     * @param button The button to add.
     */
    public void addButton(JButton button) {
        jPanel1.add(button, 0);
        jPanel1.add(Box.createHorizontalStrut(5), 1);
    }
    
    // ProgressReceiver
    
    @Override
    public void setProgress(final float progress) throws OperationCancelled {
        checkForCancellation();
        doOnEventThreadAndWait(() -> {
            progressReports++;
            long now = System.currentTimeMillis();
            long elapsed = now - start;
            float speed = elapsed / progress;
            remaining = (long) ((1.0f - progress) * speed);
            lastUpdate = now;
            if ((! timeEstimatesActivated) && (elapsed >= 60000) && (progressReports >= 10)) {
                timeEstimatesActivated = true;
            }
            if (jProgressBar1.isIndeterminate()) {
                jProgressBar1.setIndeterminate(false);
            }
            jProgressBar1.setValue(Math.round(progress * 100f));
        });
    }

    @Override
    public void exceptionThrown(final Throwable exception) {
        if (! exceptionReported) {
            // Make sure to capture the MDC context from the current thread
            final Throwable exceptionWithContext = decorateWithMdcContext(exception);
            doOnEventThreadAndWait(() -> {
                timer.stop();
                if (jProgressBar1.isIndeterminate()) {
                    jProgressBar1.setIndeterminate(false);
                }
                jButton1.setEnabled(false);
                inhibitDone = true;
                if (chainContains(exception, OperationCancelled.class)) {
                    jLabel2.setText("Cancelled");
                    if (listener != null) {
                        listener.cancelled();
                    }
                } else {
                    jLabel2.setText("Error");
                    if (listener != null) {
                        listener.exceptionThrown(exceptionWithContext);
                    }
                }
            });
            exceptionReported = true;
        }
    }

    @Override
    public void done() {
        doOnEventThreadAndWait(() -> {
            timer.stop();
            if (jProgressBar1.isIndeterminate()) {
                jProgressBar1.setIndeterminate(false);
            }
            jProgressBar1.setValue(100);
            jButton1.setEnabled(false);
            jLabel2.setText("Done");
            scrollablePanel1.removeAll();
            if (stats != null) {
                try (PrintWriter out = new PrintWriter("logs/" + FileUtils.sanitiseName(task.getName() + "-" + new Date() + ".csv"))) {
                    int second = 1;
                    out.println("second,calculated,displayed");
                    for (int[] statsRow : stats) {
                        out.println(second++ + "," + statsRow[0] + "," + statsRow[1]);
                    }
                } catch (IOException e) {
                    logger.error("I/O error while dumping statistics", e);
                }
            }
            if ((listener != null) && (! inhibitDone)) {
                listener.done(result);
            }
        });
    }
    
    @Override
    public void setMessage(final String message) throws OperationCancelled {
        checkForCancellation();
    }

    @Override
    public void checkForCancellation() throws OperationCancelled {
        if (cancelRequested) {
            throw new OperationCancelledByUser();
        }
    }

    @Override
    public void reset() throws OperationCancelled {
        checkForCancellation();
        doOnEventThreadAndWait(() -> {
            if (stats != null) {
                try {
                    try (PrintWriter out = new PrintWriter("logs/" + FileUtils.sanitiseName(task.getName() + "-" + new Date() + ".csv"))) {
                        int second = 1;
                        out.println("second,calculated,displayed");
                        for (int[] statsRow: stats) {
                            out.println(second++ + "," + statsRow[0] + "," + statsRow[1]);
                        }
                    }
                } catch (IOException e) {
                    logger.error("I/O error while dumping statistics", e);
                }
                stats = new ArrayList<>();
            }
            jProgressBar1.setIndeterminate(true);
            start = System.currentTimeMillis();
            progressReports = 0;
            lastReportedMinutes = Integer.MAX_VALUE;
            timeEstimatesActivated = false;
            jLabel2.setText(" ");
        });
    }

    @Override
    public void subProgressStarted(SubProgressReceiver subProgressReceiver) throws OperationCancelled {
        checkForCancellation();
        doOnEventThreadAndWait(() -> {
            ProgressViewer progressViewer = new ProgressViewer(subProgressReceiver);
            ProgressReceiver parent = subProgressReceiver.getParent();
            if (parent == null) {
                // No parent; insert at start
                scrollablePanel1.add(progressViewer, 0);
            } else {
                boolean parentFound = false;
                do {
                    for (int i = 0; i < scrollablePanel1.getComponentCount(); i++) {
                        Component component = scrollablePanel1.getComponent(i);
                        ProgressViewer parentViewer = (ProgressViewer) ((component instanceof ProgressViewer) ? component : ((JPanel) component).getComponent(1));
                        if (parentViewer.getSubProgressReceiver() == parent) {
                            // Progress viewer for parent found; insert below
                            Integer parentIndentation = (Integer) parentViewer.getClientProperty(CLIENT_PROPERTY_INDENTATION);
                            int indentation = (parentIndentation != null) ? parentIndentation + 1 : 1;
                            JPanel progressPanel = new JPanel();
                            progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.LINE_AXIS));
                            progressPanel.add(Box.createHorizontalStrut(indentation * INDENTATION_SIZE));
                            progressPanel.add(progressViewer);
                            scrollablePanel1.add(progressPanel, i + 1);
                            parentFound = true;
                            break;
                        }
                    }
                    if (parent instanceof SubProgressReceiver) {
                        parent = ((SubProgressReceiver) parent).getParent();
                    } else {
                        parent = null;
                    }
                } while ((! parentFound) && (parent != null));
                if (! parentFound) {
                    // Progress viewer not found for any ancestor; append to end
                    scrollablePanel1.add(progressViewer);
                }
            }

            subProgressReceiver.addListener(new ProgressReceiver() {
                @Override
                public void setProgress(float progress) {
                    if (progress >= 1.0f) {
                        doOnEventThreadAndWait(() -> removeViewerHierarchy(subProgressReceiver));
                    }
                }

                @Override
                public void exceptionThrown(Throwable exception) {
                    doOnEventThreadAndWait(() -> removeViewerHierarchy(subProgressReceiver));
                }

                @Override
                public void done() {
                    doOnEventThreadAndWait(() -> removeViewerHierarchy(subProgressReceiver));
                }

                /**
                 * Remove a particular viewer, and any children which may
                 * still exist (this happens in the wild; not entirely clear
                 * why; may be because they never started any progress;
                 * perhaps some kind of race condition).
                 */
                private void removeViewerHierarchy(SubProgressReceiver subProgressReceiver) {
                    // Remove any children
                    for (Component component: scrollablePanel1.getComponents()) {
                        ProgressViewer viewer = (ProgressViewer) ((component instanceof ProgressViewer) ? component : ((JPanel) component).getComponent(1));
                        if (viewer.getSubProgressReceiver().getParent() == subProgressReceiver) {
                            if (logger.isTraceEnabled()) {
                                logger.trace("Progress receiver still has child; removing child. Stack trace is of child creation", viewer.getSubProgressReceiver().getCreationTrace());
                            }
                            removeViewerHierarchy(viewer.getSubProgressReceiver());
                        }
                    }

                    // Remove the viewer for this sub progress receiver
                    // itself
                    for (Component component: scrollablePanel1.getComponents()) {
                        ProgressViewer viewer = (ProgressViewer) ((component instanceof ProgressViewer) ? component : ((JPanel) component).getComponent(1));
                        if (viewer.getSubProgressReceiver() == subProgressReceiver) {
                            scrollablePanel1.remove(component);
                            break;
                        }
                    }

                    jScrollPane1.validate();
                }

                @Override public void setMessage(String message) {}
                @Override public void checkForCancellation() {}
                @Override public void reset() {}
                @Override public void subProgressStarted(SubProgressReceiver subProgressReceiver) {}
            });
            jScrollPane1.validate();
        });
    }

    // ActionListener
    
    @Override
    public void actionPerformed(ActionEvent e) {
        if (timeEstimatesActivated) {
            long now = System.currentTimeMillis();
            remaining -= (now - lastUpdate);
            lastUpdate = now;
            int minutes = (int) (remaining / 60000);
            if ((minutes < lastReportedMinutes) || (minutes > (lastReportedMinutes + 1))) {
                lastReportedMinutes = minutes;
                if (minutes < 1) {
                    jLabel2.setText("Less than a minute remaining");
                } else if (minutes < 90) {
                    jLabel2.setText("About " + (minutes + 1) + " minutes remaining");
                } else {
                    int hours = (minutes + 30) / 60;
                    jLabel2.setText("About " + hours + " hours remaining");
                }
            }
            if (stats != null) {
                stats.add(new int[] {minutes, lastReportedMinutes + 1});
            }
        } else if (stats != null) {
            stats.add(new int[] {-1, -1});
        }
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // //GEN-BEGIN:initComponents
    private void initComponents() {

        jProgressBar1 = new javax.swing.JProgressBar();
        jLabel2 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        scrollablePanel1 = new org.pepsoft.util.swing.ScrollablePanel();
        jPanel1 = new javax.swing.JPanel();
        jButton1 = new javax.swing.JButton();

        jLabel2.setText(" ");

        jScrollPane1.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        scrollablePanel1.setLayout(new java.awt.GridLayout(0, 1));
        jScrollPane1.setViewportView(scrollablePanel1);

        jPanel1.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.TRAILING, 0, 0));

        jButton1.setText("Cancel");
        jButton1.setEnabled(false);
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });
        jPanel1.add(jButton1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel2)
                .addGap(0, 0, 0)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, 382, Short.MAX_VALUE)))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 127, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel2)
                    .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
        );
    }// //GEN-END:initComponents

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
        cancelRequested = true;
        jButton1.setEnabled(false);
    }//GEN-LAST:event_jButton1ActionPerformed
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButton1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JProgressBar jProgressBar1;
    private javax.swing.JScrollPane jScrollPane1;
    private org.pepsoft.util.swing.ScrollablePanel scrollablePanel1;
    // End of variables declaration//GEN-END:variables

    private ProgressTask task;
    private volatile boolean cancelRequested, exceptionReported;
    private volatile T result;
    private long start, remaining, lastUpdate;
    private int progressReports, lastReportedMinutes = Integer.MAX_VALUE;
    private Timer timer;
    private Listener listener;
    private boolean timeEstimatesActivated, inhibitDone, cancelable = true;
    private List stats;

    private static final String CLIENT_PROPERTY_INDENTATION = MultiProgressComponent.class.getName() + ".indentation";
    private static final int INDENTATION_SIZE = 32;
    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MultiProgressComponent.class);
    private static final long serialVersionUID = 1L;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy