// $Id: BrissGUI.java 70 2012-05-26 16:20:19Z laborg $
/**
 * Copyright 2010, 2011 Gerhard Aigner, Rastislav Wartiak
 * <p>
 * This file is part of BRISS.
 * <p>
 * BRISS is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * <p>
 * BRISS 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 General Public License for more details.
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * BRISS. If not, see http://www.gnu.org/licenses/.
 */
package at.laborg.briss;

import at.laborg.briss.exception.CropException;
import at.laborg.briss.gui.HelpDialog;
import at.laborg.briss.gui.MergedPanel;
import at.laborg.briss.gui.WrapLayout;
import at.laborg.briss.model.ClusterDefinition;
import at.laborg.briss.model.CropDefinition;
import at.laborg.briss.model.PageCluster;
import at.laborg.briss.model.PageExcludes;
import at.laborg.briss.model.WorkingSet;
import at.laborg.briss.utils.BrissFileHandling;
import at.laborg.briss.utils.ClusterCreator;
import at.laborg.briss.utils.ClusterRenderWorker;
import at.laborg.briss.utils.DesktopHelper;
import at.laborg.briss.utils.DocumentCropper;
import at.laborg.briss.utils.FileDrop;
import at.laborg.briss.utils.PageNumberParser;
import com.itextpdf.text.DocumentException;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.stage.FileChooser;
import org.jpedal.exception.PdfException;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author gerhard, hybridtupel
 */

public class BrissGUI extends JFrame implements PropertyChangeListener, ComponentListener {

    private static final long serialVersionUID = 5623134571633965275L;
    private static final int DEFAULT_HEIGHT = 600;
    private static final int DEFAULT_WIDTH = 800;
    private static final int MIN_HEIGHT = 400;
    private static final int MIN_WIDTH = 400;

    private static final String DONATION_URI = "http://sourceforge.net/project/project_donations.php?group_id=320676"; //$NON-NLS-1$
    private static final String RES_ICON_PATH = "Briss_icon_032x032.gif"; //$NON-NLS-1$
    private static final String RES_DROP_IMG_PATH = "drop.png"; //$NON-NLS-1$
    private static final String PROGRESS = "progress";

    private JMenuBar menuBar;
    private JPanel previewPanel;
    private JProgressBar progressBar;
    private JMenuItem loadButton, showHelpButton, openDonationLinkButton, exitButton;
    private List<MergedPanel> mergedPanels = null;

    private File lastOpenDir;

    private WorkingSet workingSet;
    private final FileChooser fileChooser;
    private DragAndDropPanel dndPanel;
    private JScrollPane scrollPane;
    private CardLayout cardLayout;
    private JPanel wrapperPanel;
    private JButton showPreview;
    private JButton startCropping;

    public BrissGUI(String[] args) {
        super(Messages.getString("BrissGUI.windowTitle")); //$NON-NLS-1$
        init();
        tryToLoadFileFromArgument(args);


        new JFXPanel(); // used for initializing javafx thread
        fileChooser = new FileChooser();
        initFileChooser();
    }

    private void initFileChooser() {
        fileChooser.setInitialDirectory(lastOpenDir);
        FileChooser.ExtensionFilter pdfFilter = new FileChooser.ExtensionFilter("PDF files (*.pdf)", "*.pdf");
        fileChooser.getExtensionFilters().add(pdfFilter);
    }

    private void tryToLoadFileFromArgument(String[] args) {
        if (args.length == 0)
            return;
        File fileArg = new File(args[0]);
        if (fileArg.exists() && fileArg.getAbsolutePath().trim().endsWith(".pdf")) { //$NON-NLS-1$
            try {
                importNewPdfFile(fileArg);
            } catch (IOException e) {
                JOptionPane.showMessageDialog(this, e.getMessage(),
                    Messages.getString("BrissGUI.brissError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
            } catch (PdfException e) {
                JOptionPane.showMessageDialog(this, e.getMessage(),
                    Messages.getString("BrissGUI.brissError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
            }
        }

    }

    private void init() {

        setDefaultCloseOperation(EXIT_ON_CLOSE);

        this.setTransferHandler(new BrissTransferHandler(this));

        setUILook();

        loadAppIcon();

        // Create the menu bar.
        menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu(Messages.getString("BrissGUI.file")); //$NON-NLS-1$
        fileMenu.setMnemonic(KeyEvent.VK_F);
        JMenu helpMenu = new JMenu("Help");
        helpMenu.setMnemonic(KeyEvent.VK_H);

        menuBar.add(fileMenu);
        menuBar.add(helpMenu);

        loadButton = new JMenuItem(Messages.getString("BrissGUI.loadFile"), KeyEvent.VK_L); //$NON-NLS-1$
        loadButton.setEnabled(true);
        loadButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, 0));
        loadButton.addActionListener(a -> showOpenFileDialog());
        fileMenu.add(loadButton);

        fileMenu.addSeparator();

        exitButton = new JMenuItem(Messages.getString("BrissGUI.exit"), KeyEvent.VK_E); //$NON-NLS-1$
        exitButton.addActionListener(a -> System.exit(0));
        fileMenu.add(exitButton);

        openDonationLinkButton = new JMenuItem(Messages.getString("BrissGUI.donate")); //$NON-NLS-1$
        openDonationLinkButton.addActionListener(a -> {
            try {
                if (Desktop.isDesktopSupported()) {
                    Desktop.getDesktop().browse(URI.create(DONATION_URI));
                }
            } catch (IOException e) {
                // Ignore error
                e.printStackTrace();
            }
        });
        helpMenu.add(openDonationLinkButton);

        showHelpButton = new JMenuItem(Messages.getString("BrissGUI.showHelp")); //$NON-NLS-1$
        showHelpButton.addActionListener(a -> new HelpDialog(this, Messages.getString("BrissGUI.brissHelp"), Dialog.ModalityType.MODELESS));
        helpMenu.add(showHelpButton);

        setJMenuBar(menuBar);

        previewPanel = new JPanel();
        previewPanel.setLayout(new WrapLayout(FlowLayout.LEFT, 4, 4));
        previewPanel.setEnabled(true);
        previewPanel.setBackground(new Color(186, 186, 186));
        previewPanel.addComponentListener(this);

        progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true);
        progressBar.setPreferredSize(new Dimension(400, 30));
        progressBar.setVisible(false);

        startCropping = new JButton("Crop PDF");
        startCropping.setVisible(false);
        startCropping.addActionListener(a -> startCropping());

        showPreview = new JButton(("Preview"));
        showPreview.addActionListener(a -> showPreview());
        showPreview.setVisible(false);

        scrollPane = new JScrollPane(previewPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setVisible(true);
        scrollPane.setBorder(null);
        scrollPane.getVerticalScrollBar().setUnitIncrement(30);

        dndPanel = new DragAndDropPanel(loadDragAndDropLabelImage(), e -> showOpenFileDialog());
        new FileDrop(dndPanel, true, files -> {
            if (files.length == 1) {
                File file = files[0];
                if (file.getName().toLowerCase().endsWith(".pdf")) {
                    loadPDF(file);
                }
            }
        });

        cardLayout = new CardLayout();
        wrapperPanel = new JPanel(cardLayout);
        wrapperPanel.add(scrollPane, "scroll");
        wrapperPanel.add(dndPanel, "dnd");
        add(wrapperPanel, BorderLayout.CENTER);

        cardLayout.last(wrapperPanel);

        JPanel footer = new JPanel();
        footer.add(progressBar);
        footer.add(showPreview);
        footer.add(startCropping);
        add(footer, BorderLayout.PAGE_END);

        setWindowBounds();
        pack();
        setVisible(true);
    }

    private void startCropping() {
        showSaveFileDialog();
    }

    private void setWindowBounds() {
        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int screenWidth = gd.getDisplayMode().getWidth();
        int screenHeight = gd.getDisplayMode().getHeight();
        setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
        setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
        setLocation(screenWidth / 2 - DEFAULT_WIDTH / 2, screenHeight / 2 - DEFAULT_HEIGHT / 2);
    }

    private void setUILook() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (UnsupportedLookAndFeelException ex) {
            System.out.println(Messages.getString("BrissGUI.lookAndFeelError")); //$NON-NLS-1$
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        }
    }

    private void loadAppIcon() {
        ImageIcon icon = new ImageIcon(getClass().getClassLoader().getResource(RES_ICON_PATH));
        setIconImage(icon.getImage());
    }

    private ImageIcon loadDragAndDropLabelImage() {
        ImageIcon icon = new ImageIcon(getClass().getClassLoader().getResource(RES_DROP_IMG_PATH));
        return icon;
    }

    private static PageExcludes getExcludedPages() {
        boolean inputIsValid = false;
        String previousInput = ""; //$NON-NLS-1$

        // repeat show_dialog until valid input or canceled
        while (!inputIsValid) {
            String input = JOptionPane.showInputDialog(Messages.getString("BrissGUI.excludedPagesInfo"), previousInput);
            previousInput = input;

            if (input == null || input.equals("")) //$NON-NLS-1$
                return null;

            try {
                PageExcludes pageExcludes = new PageExcludes(PageNumberParser.parsePageNumber(input));
                return pageExcludes;
            } catch (ParseException e) {
                JOptionPane.showMessageDialog(null, e.getMessage(),
                    Messages.getString("BrissGUI.inputError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
            }

        }
        return null;
    }

    private void showSaveFileDialog() {
        Platform.runLater(() -> {
            // Open JavaFX file chooser in FX-thread which is not as ugly as the swing one
            initFileChooser();
            fileChooser.setTitle("Save cropped PDF File");
            fileChooser.setInitialFileName(BrissFileHandling.getRecommendedFileName(workingSet.getSourceFile()));
            File file = fileChooser.showSaveDialog(null);
            if (file != null) {
                SwingUtilities.invokeLater(() -> savePDF(file));
            }
        });
    }

    private void showOpenFileDialog() {
        Platform.runLater(() -> {
            initFileChooser();
            // Open JavaFX file chooser in FX-thread which is not as ugly as the swing one
            fileChooser.setTitle("Open PDF File");
            File file = fileChooser.showOpenDialog(null);
            if (file != null) {
                SwingUtilities.invokeLater(() -> loadPDF(file));
            }
        });
    }

    private void loadPDF(File file) {
        try {
            importNewPdfFile(file);
            setTitle("BRISS - " + file.getName()); //$NON-NLS-1$
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, e.getMessage(), Messages.getString("BrissGUI."), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
        } catch (PdfException e) {
            JOptionPane.showMessageDialog(this, e.getMessage(),
                Messages.getString("BrissGUI.loadingError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
        }
    }

    private void savePDF(File file) {
        setWorkingState(Messages.getString("BrissGUI.loadingPDF")); //$NON-NLS-1$
        try {
            CropDefinition cropDefinition = CropDefinition.createCropDefinition(workingSet.getSourceFile(),
                file, workingSet.getClusterDefinition());
            File result = DocumentCropper.crop(cropDefinition);
            if (result != null) {
                DesktopHelper.openFileWithDesktopApp(result);
                lastOpenDir = result.getParentFile();
            }

        } catch (IOException | DocumentException | CropException e) {
            JOptionPane.showMessageDialog(this, e.getMessage(),
                Messages.getString("BrissGUI.croppingError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
        } finally {
            setIdleState(); //$NON-NLS-1$
        }
    }

    private void showPreview() {
        try {
            setWorkingState(Messages.getString("BrissGUI.createShowPreview")); //$NON-NLS-1$
            File result = createAndExecuteCropJobForPreview();
            DesktopHelper.openFileWithDesktopApp(result);
        } catch (IOException | DocumentException | CropException e) {
            JOptionPane.showMessageDialog(this, e.getMessage(),
                Messages.getString("BrissGUI.croppingError"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
        } finally {
            setIdleState(); //$NON-NLS-1$
        }
    }

    private File createAndExecuteCropJobForPreview() throws IOException, DocumentException, CropException {
        File tmpCropFileDestination = File.createTempFile("briss", ".pdf"); //$NON-NLS-1$ //$NON-NLS-2$
        CropDefinition cropDefinition = CropDefinition.createCropDefinition(workingSet.getSourceFile(),
            tmpCropFileDestination, workingSet.getClusterDefinition());
        File result = DocumentCropper.crop(cropDefinition);
        return result;
    }

    private void setIdleState() {
        progressBar.setVisible(false);
        progressBar.setValue(0);
        progressBar.setString("");
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }

    private void setWorkingState(String stateMessage) {
        progressBar.setVisible(true);
        progressBar.setString(stateMessage);
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }

    protected void importNewPdfFile(File loadFile) throws IOException, PdfException {
        lastOpenDir = loadFile.getParentFile();
        previewPanel.removeAll();
        cardLayout.first(wrapperPanel);
        progressBar.setVisible(true);
        progressBar.setString(Messages.getString("BrissGUI.loadingNewFile")); //$NON-NLS-1$
        ClusterPagesTask clusterTask = new ClusterPagesTask(loadFile, null);
        clusterTask.addPropertyChangeListener(this);
        clusterTask.execute();
    }

    private void reloadWithOtherExcludes() throws IOException, PdfException {
        previewPanel.removeAll();
        progressBar.setString(Messages.getString("BrissGUI.reloadingFile")); //$NON-NLS-1$
        ClusterPagesTask clusterTask = new ClusterPagesTask(workingSet.getSourceFile(), getExcludedPages());
        clusterTask.addPropertyChangeListener(this);
        clusterTask.execute();
    }

    private void maximizeWidthInSelectedRects() {
        // maximize to width
        // search for maximum width
        int maxWidth = -1;
        for (MergedPanel panel : mergedPanels) {
            int panelMaxWidth = panel.getWidestSelectedRect();
            if (maxWidth < panelMaxWidth) {
                maxWidth = panelMaxWidth;
            }
        }
        // set maximum width to all rectangles
        if (maxWidth == -1)
            return;
        for (MergedPanel mp : mergedPanels) {
            mp.setSelCropWidth(maxWidth);
        }
    }

    private void maximizeHeightInSelectedRects() {
        // maximize to height
        // search for maximum height
        int maxHeight = -1;
        for (MergedPanel panel : mergedPanels) {
            int panelMaxHeight = panel.getHeighestSelectedRect();
            if (maxHeight < panelMaxHeight) {
                maxHeight = panelMaxHeight;
            }
        }
        // set maximum height to all rectangles
        if (maxHeight == -1)
            return;
        for (MergedPanel mp : mergedPanels) {
            mp.setSelCropHeight(maxHeight);
        }
    }

    private void maximizeSizeInAllRects() {
        // maximize to width and height for all rectangles
        // search for maximums
        int maxWidth = -1;
        int maxHeight = -1;
        for (MergedPanel panel : mergedPanels) {
            Dimension panelMaxSize = panel.getLargestRect();
            if (maxWidth < panelMaxSize.width) {
                maxWidth = panelMaxSize.width;
            }
            if (maxHeight < panelMaxSize.height) {
                maxHeight = panelMaxSize.height;
            }
        }
        // set maximum size to all rectangles
        if ((maxWidth == -1) || (maxHeight == -1))
            return;
        for (MergedPanel mp : mergedPanels) {
            mp.setAllCropSize(maxWidth, maxHeight);
        }
    }

    public void alignSelRects(int x, int y, int w, int h) {
        // set position and size of selected rectangles
        for (MergedPanel mp : mergedPanels) {
            mp.setSelCropSize(w, h);
            mp.moveToSelelectedCrops(x, y);
        }
    }

    public void moveSelectedRects(int x, int y) {
        // move selected rectangles
        // parameters are relative to current position
        for (MergedPanel mp : mergedPanels) {
            mp.moveSelelectedCrops(x, y);
        }
    }

    public void setDefinedSizeSelRects() {
        // set size of selected rectangles
        // based on user input

        String defInput = ""; //$NON-NLS-1$

        // get maximum dimensions
        int maxWidth = -1;
        int maxHeight = -1;
        for (MergedPanel panel : mergedPanels) {
            int panelMaxWidth = panel.getWidestSelectedRect();
            if (maxWidth < panelMaxWidth) {
                maxWidth = panelMaxWidth;
            }
            int panelMaxHeight = panel.getHeighestSelectedRect();
            if (maxHeight < panelMaxHeight) {
                maxHeight = panelMaxHeight;
            }
        }
        if ((maxWidth >= 0) && (maxHeight >= 0)) {
            maxWidth = Math.round(25.4f * maxWidth / 72f);
            maxHeight = Math.round(25.4f * maxHeight / 72f);
            defInput = maxWidth + " " + maxHeight; //$NON-NLS-1$
        }

        // get user input
        // maximums are used as a default
        String input = JOptionPane.showInputDialog(Messages.getString("BrissGUI.size"), defInput);

        if (input == null || input.equals("")) //$NON-NLS-1$
            return;

        String[] dims = input.split(" ", 2); //$NON-NLS-1$
        if (dims.length != 2)
            return;

        int w = -1;
        int h = -1;
        try {
            w = Integer.parseInt(dims[0]);
            h = Integer.parseInt(dims[1]);
        } catch (NumberFormatException e) {
            return;
        }

        // convert from milimeters to points
        w = Math.round(w * 72f / 25.4f);
        h = Math.round(h * 72f / 25.4f);

        for (MergedPanel mp : mergedPanels) {
            mp.setSelCropWidth(w);
            mp.setSelCropHeight(h);
        }
    }

    public void setPositionSelRects() {
        // set position of selected rectangles
        // based on user input

        String defInput = ""; //$NON-NLS-1$

        // get minimums of positions
        int minX = Integer.MAX_VALUE;
        int minY = Integer.MAX_VALUE;
        for (MergedPanel panel : mergedPanels) {
            int panelMinX = panel.getLeftmostSelectedRect();
            if (minX > panelMinX) {
                minX = panelMinX;
            }
            int panelMinY = panel.getUpmostSelectedRect();
            if (minY > panelMinY) {
                minY = panelMinY;
            }
        }
        if ((minX < Integer.MAX_VALUE) && (minY < Integer.MAX_VALUE)) {
            minX = Math.round(25.4f * minX / 72f);
            minY = Math.round(25.4f * minY / 72f);
            defInput = minX + " " + minY; //$NON-NLS-1$
        }

        // get user input
        // minimums are used as a default
        String input = JOptionPane.showInputDialog(Messages.getString("BrissGUI.position"), defInput);

        if (input == null || input.equals("")) //$NON-NLS-1$
            return;

        String[] dims = input.split(" ", 2); //$NON-NLS-1$
        if (dims.length != 2)
            return;

        int x = -1;
        int y = -1;
        try {
            x = Integer.parseInt(dims[0]);
            y = Integer.parseInt(dims[1]);
        } catch (NumberFormatException e) {
            return;
        }

        // convert from milimeters to points
        x = Math.round(x * 72f / 25.4f);
        y = Math.round(y * 72f / 25.4f);

        for (MergedPanel mp : mergedPanels) {
            mp.moveToSelelectedCrops(x, y);
        }
    }

    public void resizeSelRects(int w, int h) {
        // change size of selected rectangles (relative)
        for (MergedPanel mp : mergedPanels) {
            mp.resizeSelCrop(w, h);
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (PROGRESS.equals(evt.getPropertyName())) { //$NON-NLS-1$
            progressBar.setValue((Integer) evt.getNewValue());
        }
    }

    private void setStateAfterClusteringFinished(ClusterDefinition newClusters, PageExcludes newPageExcludes,
                                                 File newSource) {
        updateWorkingSet(newClusters, newPageExcludes, newSource);

        previewPanel.removeAll();
        mergedPanels = new ArrayList<MergedPanel>();

        for (PageCluster cluster : workingSet.getClusterDefinition().getClusterList()) {
            MergedPanel p = new MergedPanel(cluster, this);
            previewPanel.add(p);
            mergedPanels.add(p);
        }
        progressBar.setString(Messages.getString("BrissGUI.clusteringRenderingFinished")); //$NON-NLS-1$

        setIdleState(); //$NON-NLS-1$
        pack();
        setExtendedState(Frame.MAXIMIZED_BOTH);
        previewPanel.repaint();
        showPreview.setVisible(true);
        startCropping.setVisible(true);
        progressBar.setVisible(false);
        repaint();
    }

    private void updateWorkingSet(ClusterDefinition newClusters, PageExcludes newPageExcludes, File newSource) {
        if (workingSet == null) {
            // completely new
            workingSet = new WorkingSet(newSource);
        } else if (workingSet.getSourceFile().equals(newSource)) {
            // just reload with other excludes
            copyCropsToClusters(workingSet.getClusterDefinition(), newClusters);
        }
        workingSet.setSourceFile(newSource);
        workingSet.setClusters(newClusters);
        workingSet.setPageExcludes(newPageExcludes);
    }

    private void copyCropsToClusters(ClusterDefinition oldClusters, ClusterDefinition newClusters) {

        for (PageCluster newCluster : newClusters.getClusterList()) {
            for (Integer pageNumber : newCluster.getAllPages()) {
                PageCluster oldCluster = oldClusters.getSingleCluster(pageNumber);
                for (Float[] ratios : oldCluster.getRatiosList()) {
                    newCluster.addRatios(ratios);
                }
            }
        }
    }

    private class ClusterPagesTask extends SwingWorker<Void, Void> {

        private final File source;
        private final PageExcludes pageExcludes;
        private ClusterDefinition clusterDefinition = null;

        public ClusterPagesTask(File source, PageExcludes pageExcludes) {
            super();
            this.source = source;
            this.pageExcludes = pageExcludes;
        }

        @Override
        protected void done() {
            setStateAfterClusteringFinished(clusterDefinition, pageExcludes, source);
        }

        @Override
        protected Void doInBackground() {

            try {
                clusterDefinition = ClusterCreator.clusterPages(source, pageExcludes);

            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                return null;
            }

            int totalWorkUnits = clusterDefinition.getNrOfPagesToRender();
            ClusterRenderWorker renderWorker = new ClusterRenderWorker(source, clusterDefinition);
            renderWorker.start();

            while (renderWorker.isAlive()) {
                int percent = (int) ((renderWorker.workerUnitCounter / (float) totalWorkUnits) * 100);
                setProgress(percent);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                }
            }

            return null;
        }
    }

    @Override
    public void componentResized(ComponentEvent e) {
        previewPanel.revalidate();
        for (Component component : previewPanel.getComponents()) {
            component.repaint();
        }
    }

    @Override
    public void componentMoved(ComponentEvent e) {
    }

    @Override
    public void componentShown(ComponentEvent e) {
    }

    @Override
    public void componentHidden(ComponentEvent e) {
    }
}