org.alchemy.core.AlcSession.java Source code

Java tutorial

Introduction

Here is the source code for org.alchemy.core.AlcSession.java

Source

/*
 *  This file is part of the Alchemy project - http://al.chemy.org
 * 
 *  Copyright (c) 2007-2010 Karl D.D. Willis
 *  Copyright (c) 2009 Steren Giannini (steren.giannini@gmail.com)
 * 
 *  Alchemy 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.
 * 
 *  Alchemy 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.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with Alchemy.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.alchemy.core;

import java.awt.event.*;
import java.io.*;
import com.sun.pdfview.*;
import eu.medsea.util.MimeUtil;
import java.awt.Graphics2D;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
import com.lowagie.text.xml.xmp.*;

// BATIK (for svg)
import java.awt.Dimension;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.dom.GenericDOMImplementation;
//import org.w3c.dom.Document; //because of Document class conflict, do not import
import org.w3c.dom.DOMImplementation;

/**
 * Class to control Alchemy 'sessions'
 * Timing, recording, loading of PDF drawing sessions 
 */
class AlcSession implements ActionListener, AlcConstants {

    /** Recording Timer */
    private javax.swing.Timer timer;
    /** Recording on or off */
    private boolean recordState;
    /** PDF write file */
    private File pdfWriteFile;
    /** Record Indicator Timer */
    private javax.swing.Timer indicatorTimer;
    /** PDF read file */
    private PDFFile pdfReadFile;
    /** PDF read page */
    PDFPage pdfReadPage;
    /**  Current page of the read PDF */
    private int currentPdfReadPage = 1;
    /** Number of pages of the read PDF */
    private int maxPdfReadPage;

    AlcSession() {
    }

    //////////////////////////////////////////////////////////////
    // RECORDING / TIMER
    //////////////////////////////////////////////////////////////
    /** Start or end recording
     * @param record    The new recording state
     */
    void setRecording(boolean record) {
        if (record) {

            int interval = Alchemy.preferences.sessionRecordingInterval;
            //Set up timer to save pages into the pdf
            if (timer == null) {
                timer = new javax.swing.Timer(interval, this);
                timer.start();
            } else {
                if (timer.isRunning()) {
                    timer.stop();
                }
                timer.setDelay(Alchemy.preferences.sessionRecordingInterval);
                timer.start();

            }
            //Alchemy.canvas.resetCanvasChanged();

        } else {

            if (timer != null) {
                // if it is running then stop it
                if (timer.isRunning()) {
                    timer.stop();
                }
            }

        }
        //Remember the record start
        recordState = record;
    }

    /** Return if currently recording
     * @return
     */
    boolean isRecording() {
        return recordState;
    }

    /** Set the session timer interval
     * @param interval  An interval in milliseconds
     */
    void setTimerInterval(int interval) {
        System.out.println("Interval: " + interval);
        // Set the interval in the preferences
        Alchemy.preferences.sessionRecordingInterval = interval;
        // If recording is on
        if (recordState) {
            // Check if the timer has been initialised, if not don't do anything extra
            if (timer != null) {
                // if it is running then stop it, set the interval then restart it
                if (timer.isRunning()) {
                    timer.stop();
                    timer = null;
                    if (interval > 0) {
                        timer = new javax.swing.Timer(interval, this);
                        timer.start();
                    }
                } else {
                    timer = null;
                    timer = new javax.swing.Timer(interval, this);
                    timer.start();
                }
            } else {
                if (interval > 0) {
                    timer = new javax.swing.Timer(interval, this);
                    timer.start();
                }
            }
        }
    }

    /** Restart the session timer */
    private void restartTimer() {
        if (timer != null) {
            if (timer.isRunning()) {
                System.out.println("Timer Restarted");
                timer.restart();
            }
        }
    }

    /** Restart the session */
    void restartSession() {
        pdfWriteFile = null;
    }

    //////////////////////////////////////////////////////////////
    // SAVE PDF 
    //////////////////////////////////////////////////////////////
    /** Return the current file being created by the pdf */
    File getCurrentPdfPath() {
        return pdfWriteFile;
    }

    /** Manually save a pdf page then restart the timer */
    void manualSavePage() {
        savePage();
        restartTimer();
        progressPage();
    }

    /** Manually save a pdf page, then clear, then restart the timer */
    void manualSaveClearPage() {
        saveClearPage();
        restartTimer();
    }

    /** Save a single pdf page to the current pdf being created */
    boolean savePage() {
        // If this is the first time or if the file is not actually there
        if (pdfWriteFile == null || !pdfWriteFile.exists()) {
            String fileName = Alchemy.preferences.sessionFilePreName
                    + AlcUtil.dateStamp(Alchemy.preferences.sessionFileDateFormat) + ".pdf";
            pdfWriteFile = new File(Alchemy.preferences.sessionPath, fileName);
            System.out.println("Current PDF file: " + pdfWriteFile.getPath());
            return saveSinglePdf(pdfWriteFile);

            // Else save a temp file then join the two together
        } else {

            try {
                File temp = File.createTempFile("AlchemyPage", ".pdf");
                // Delete temp file when program exits.
                //temp.deleteOnExit();
                // Make the temp pdf
                saveSinglePdf(temp);
                boolean jointUp = addPageToPdf(pdfWriteFile, temp);
                if (jointUp) {
                    //System.out.println("Pdf files joint");
                    temp.delete();
                    return true;
                }

            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return false;
    }

    /** Save a single pdfReadPage to the current pdf being created, then clear the canvas */
    void saveClearPage() {
        savePage();
        Alchemy.canvas.clear();
        progressPage();
    }

    /** Save the canvas to a single paged PDF file
     * 
     * @param file  The file object to save the pdf to
     * @return      True if save worked, otherwise false
     */
    boolean saveSinglePdf(File file) {
        // Get the current 'real' size of the canvas without margins/borders
        java.awt.Rectangle bounds = Alchemy.canvas.getVisibleRect();
        //int singlePdfWidth = Alchemy.window.getWindowSize().width;
        //int singlePdfHeight = Alchemy.window.getWindowSize().height;
        com.lowagie.text.Document document = new com.lowagie.text.Document(
                new com.lowagie.text.Rectangle(bounds.width, bounds.height), 0, 0, 0, 0);
        System.out.println("Save Single Pdf Called: " + file.toString());
        boolean noError = true;

        try {

            PdfWriter singleWriter = PdfWriter.getInstance(document, new FileOutputStream(file));
            document.addTitle("Alchemy Session");
            document.addAuthor(USER_NAME);
            document.addCreator("Alchemy <http://al.chemy.org>");

            // Add metadata and open the document
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            XmpWriter xmp = new XmpWriter(os);
            PdfSchema pdf = new PdfSchema();
            pdf.setProperty(PdfSchema.KEYWORDS, "Alchemy <http://al.chemy.org>");
            //pdf.setProperty(PdfSchema.VERSION, "1.4");
            xmp.addRdfDescription(pdf);
            xmp.close();
            singleWriter.setXmpMetadata(os.toByteArray());

            // To avoid transparent colurs being converted from RGB>CMYK>RGB
            // We have to add everything to a transparency group
            PdfTransparencyGroup transGroup = new PdfTransparencyGroup();
            transGroup.put(PdfName.CS, PdfName.DEVICERGB);

            document.open();

            PdfContentByte cb = singleWriter.getDirectContent();
            PdfTemplate tp = cb.createTemplate(bounds.width, bounds.height);

            document.newPage();

            cb.getPdfWriter().setGroup(transGroup);
            // Make sure the color space is Device RGB
            cb.setDefaultColorspace(PdfName.CS, PdfName.DEVICERGB);

            // Draw into the template and add it to the PDF 
            Graphics2D g2pdf = tp.createGraphics(bounds.width, bounds.height);
            Alchemy.canvas.setGuide(false);
            Alchemy.canvas.vectorCanvas.paintComponent(g2pdf);
            Alchemy.canvas.setGuide(true);
            g2pdf.dispose();
            cb.addTemplate(tp, 0, 0);

        } catch (DocumentException ex) {
            System.err.println(ex);
            noError = false;
        } catch (IOException ex) {
            System.err.println(ex);
            noError = false;
        }

        document.close();

        return noError;
    }

    /** Save the canvas to a single paged SVG file
     *
     * @param file  The file object to save the svg to
     * @return      True if save worked, otherwise false
     */
    boolean saveSVG(File file) {
        boolean noError = true;
        System.out.println("Save SVG Called: " + file.toString());

        // Get the current 'real' size of the canvas without margins/borders
        java.awt.Rectangle bounds = Alchemy.canvas.getVisibleRect();
        // Get a DOMImplementation.
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
        // Create an instance of org.w3c.dom.Document.
        String svgNS = "http://www.w3.org/2000/svg";
        org.w3c.dom.Document document = domImpl.createDocument(svgNS, "svg", null);
        //Set custom comment
        SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
        ctx.setComment("Generated by Alchemy (http://al.chemy.org) with Batik SVG Generator");

        // Create an instance of the SVG Generator.
        SVGGraphics2D svgGenerator = new SVGGraphics2D(ctx, false);

        //set the canvas size
        svgGenerator.setSVGCanvasSize(new Dimension(bounds.width, bounds.height));
        // Ask vectorCanvas to render into the SVG Graphics2D implementation.
        Alchemy.canvas.setGuide(false);
        Alchemy.canvas.vectorCanvas.paintComponent(svgGenerator);
        Alchemy.canvas.setGuide(true);
        svgGenerator.dispose();

        boolean useCSS = true; // we want to use CSS style attributes

        //Write to the file
        FileWriter out;
        try {
            out = new FileWriter(file);
            svgGenerator.stream(out, useCSS);
        } catch (IOException ex) {
            System.err.println(ex);
            noError = false;
        }

        return noError;
    }

    /** Adds a pdfReadPage to an existing pdf file
     * 
     * @param mainPdf   The main pdf with multiple pages.
     *                  Also used as the destination file.
     * @param tempPdf   The 'new' pdf with one pdfReadPage to be added to the main pdf
     * @return
     */
    boolean addPageToPdf(File mainPdf, File tempPdf) {
        try {
            // Destination file created in the temp dir then we will move it
            File dest = new File(DIR_TEMP, "Alchemy.pdf");
            OutputStream output = new FileOutputStream(dest);

            PdfReader reader = new PdfReader(mainPdf.getPath());
            PdfReader newPdf = new PdfReader(tempPdf.getPath());

            // See if the size of the canvas has increased
            // Size of the most recent temp PDF
            com.lowagie.text.Rectangle currentSize = newPdf.getPageSizeWithRotation(1);
            // Size of the session pdf at present
            com.lowagie.text.Rectangle oldSize = reader.getPageSizeWithRotation(1);
            // Sizes to be used from now on
            float pdfWidth = oldSize.getWidth();
            float pdfHeight = oldSize.getHeight();
            if (currentSize.getWidth() > pdfWidth) {
                pdfWidth = currentSize.getWidth();
            }
            if (currentSize.getHeight() > pdfHeight) {
                pdfHeight = currentSize.getHeight();
            }

            // Use the new bigger canvas size if required
            com.lowagie.text.Document document = new com.lowagie.text.Document(
                    new com.lowagie.text.Rectangle(pdfWidth, pdfHeight), 0, 0, 0, 0);
            PdfCopy copy = new PdfCopy(document, output);

            // Copy the meta data
            document.addTitle("Alchemy Session");
            document.addAuthor(USER_NAME);
            document.addCreator("Alchemy <http://al.chemy.org>");
            copy.setXmpMetadata(reader.getMetadata());
            document.open();

            // Holds the PDF
            PdfContentByte cb = copy.getDirectContent();

            // Add each page from the main PDF
            for (int i = 0; i < reader.getNumberOfPages();) {
                ++i;
                document.newPage();
                cb.setDefaultColorspace(PdfName.CS, PdfName.DEVICERGB);
                PdfImportedPage page = copy.getImportedPage(reader, i);
                copy.addPage(page);
            }
            // Add the last (new) page
            document.newPage();
            PdfImportedPage lastPage = copy.getImportedPage(newPdf, 1);
            copy.addPage(lastPage);
            output.flush();
            document.close();
            output.close();

            if (dest.exists()) {
                // Save the location of the main pdf
                String mainPdfPath = mainPdf.getPath();
                // Delete the old file
                if (mainPdf.exists()) {
                    mainPdf.delete();
                }
                // The final joined up pdf file
                File joinPdf = new File(mainPdfPath);
                // Rename the file
                boolean success = dest.renameTo(joinPdf);
                if (!success) {
                    System.err.println("Error moving Pdf");
                    return false;
                }

            } else {
                System.err.println("File does not exist?!: " + dest.getAbsolutePath());
                return false;
            }
            return true;

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //////////////////////////////////////////////////////////////
    // LOAD PDF
    //////////////////////////////////////////////////////////////
    /** Load a session file to draw on top of */
    boolean loadSessionFile(File file) {

        try {

            // Check this is a pdf file
            String mime = MimeUtil.getMimeType(file.getAbsoluteFile());
            if (!mime.equals("application/pdf")) {
                AlcUtil.showConfirmDialogFromBundle("notPDFDialogTitle", "notPDFDialogMessage");
                return false;
            }

            // First make sure we are not loading the current session file
            if (file.equals(pdfWriteFile)) {

                boolean result = AlcUtil.showConfirmDialogFromBundle("loadSessionPDFDialogTitle",
                        "loadSessionPDFDialogMessage");

                if (result) {
                    restartSession();
                } else {
                    return false;
                }

            }

            // Secondly check the meta data to see if this is an Alchemy session
            PdfReader reader = new PdfReader(file.getPath());
            String metaData = new String(reader.getMetadata());

            // If the pdf is not an Alchemy pdf
            if (!metaData.contains("Alchemy") && !file.getName().startsWith("Alchemy")) {

                boolean result = AlcUtil.showConfirmDialogFromBundle("loadForeignPDFDialogTitle",
                        "loadForeignPDFDialogMessage");
                if (!result) {
                    return false;
                }
            }

            // set up the PDF reading
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            FileChannel channel = raf.getChannel();
            ByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
            pdfReadFile = new PDFFile(buf);
            currentPdfReadPage = 1;
            maxPdfReadPage = pdfReadFile.getNumPages();
            pdfReadPage = pdfReadFile.getPage(currentPdfReadPage);
            Alchemy.canvas.redraw(true);
            return true;

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return false;
    }

    /** Move to the next pdfReadPage in the session file */
    void nextPage() {
        if (pdfReadFile != null) {
            if (currentPdfReadPage + 1 <= maxPdfReadPage) {
                currentPdfReadPage++;
                //System.out.println(currentPdfReadPage + " " + maxPdfReadPage);
                pdfReadPage = pdfReadFile.getPage(currentPdfReadPage);
                Alchemy.canvas.redraw(true);
            } else {
                TOOLKIT.beep();
            }
        }
    }

    /** Move to the previous pdfReadPage in the session file */
    void previousPage() {
        if (pdfReadFile != null) {
            if (currentPdfReadPage - 1 >= 1) {
                currentPdfReadPage--;
                //System.out.println(currentPdfReadPage + " " + maxPdfReadPage);
                pdfReadPage = pdfReadFile.getPage(currentPdfReadPage);
                Alchemy.canvas.redraw(true);
            } else {
                TOOLKIT.beep();
            }
        }
    }

    /** Unload the session file and redraw the canvas */
    void unloadSessionFile() {
        pdfReadFile = null;
        pdfReadPage = null;
        Alchemy.canvas.redraw(true);
    }

    /** When a session pdf is loaded and linked to the current pdf
     *  this function is used to loop through the pages
     */
    void progressPage() {
        // Go to the next page if the session pdf is linked
        if (Alchemy.preferences.sessionLink && pdfReadFile != null) {
            if (currentPdfReadPage + 1 <= maxPdfReadPage) {
                currentPdfReadPage++;
                //System.out.println(currentPdfReadPage + " " + maxPdfReadPage);
                pdfReadPage = pdfReadFile.getPage(currentPdfReadPage);
                Alchemy.canvas.redraw(true);
            } else {
                currentPdfReadPage = 0;
                pdfReadPage = pdfReadFile.getPage(currentPdfReadPage);
                Alchemy.canvas.redraw(true);
            }
        }
    }

    // Called by the timer
    public void actionPerformed(ActionEvent e) {
        // If the canvas has changed
        if (Alchemy.canvas.canvasChanged()) {

            // If the pdfReadPage has been saved
            if (savePage()) {
                // Show this with a small red circle on the canvas
                Alchemy.canvas.setRecordIndicator(true);

                if (indicatorTimer == null) {
                    indicatorTimer = new javax.swing.Timer(500, new ActionListener() {

                        public void actionPerformed(ActionEvent e) {
                            //System.out.println("indicatorTimer action called");
                            Alchemy.canvas.setRecordIndicator(false);
                            Alchemy.canvas.redraw();
                            indicatorTimer.stop();
                            indicatorTimer = null;
                            Alchemy.canvas.resetCanvasChanged();
                        }
                    });
                    indicatorTimer.start();
                }

                Alchemy.canvas.redraw();

                if (Alchemy.preferences.sessionAutoClear) {
                    Alchemy.canvas.clear();
                }
                progressPage();
                // This may not be needed, because of the 
                Alchemy.canvas.resetCanvasChanged();
            }
        }
    }
}