package javacodebook.media.print.text;

import java.awt.*;
import java.awt.geom.*;
import java.awt.print.*;
import java.io.*;
import javax.swing.*;

/**
 * Die Klasse TextPrinter stellt einen einfachen Rahmen für das Ausdrucken von
 * Texten zur Verfügung. Eine JEditorPane wird in einer JScrollPane angezeigt,
 * mit einem einfachen Menü kann eine Datei geöffnet und angezeigt sowie
 * gedruckt werden.
 */
public class TextPrinter extends javax.swing.JFrame {

    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JMenuItem jMenuItem2;
    private javax.swing.JEditorPane textEditorPane;
    private javax.swing.JFileChooser fileChooser;
    private javax.swing.JMenuItem jMenuItem1;
    private javax.swing.JMenu jMenu1;
    private javax.swing.JMenuBar jMenuBar1;
    private String fileName;

    /**
     * Der Konstruktor ruft die Methode initComponents() auf und gibt
     * eine DefaultCloseOperation an.
     */
    public TextPrinter() {
        initComponents();
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    }

    /**
     * Hier werden die Komponenten auf dem Frame angeordnet. Das Menü wird
     * aufgebaut und Aktionen für die Menüpunkte angegeben.
     */
    private void initComponents() {
        fileChooser = new javax.swing.JFileChooser();
        jScrollPane1 = new javax.swing.JScrollPane();
        textEditorPane = new javax.swing.JEditorPane();
        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu1 = new javax.swing.JMenu();
        jMenuItem1 = new javax.swing.JMenuItem();
        jMenuItem2 = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

        textEditorPane.setPreferredSize(new java.awt.Dimension(640, 480));
        jScrollPane1.setViewportView(textEditorPane);

        getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);

        jMenu1.setText("Datei");
        jMenuItem1.setText("\u00d6ffnen");
        jMenuItem1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showOpenFileDialog(evt);
            }
        });

        jMenu1.add(jMenuItem1);

        jMenuItem2.setText("Drucken");
        jMenuItem2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                printDocument(evt);
            }
        });

        jMenu1.add(jMenuItem2);

        jMenuBar1.add(jMenu1);

        setJMenuBar(jMenuBar1);

        pack();
    }

    /**
     * Die printDocument-Methode erzeugt einen PrinterJob und eine anonyme
     * Klasse, die das Interface Printable implementiert. Hier werden alle
     * Berechnungen für den Ausdruck des Textes vorgenommen.
     */
    private void printDocument(java.awt.event.ActionEvent evt) {
        try {
            PrinterJob job = PrinterJob.getPrinterJob();
            job.setJobName("Textausdruck - " + fileName);
            job.setCopies(1);
            job.setPrintable(new Printable() {
                /**
                 * Die print()-Methode wird für jede einzelne Seite des Ausdrucks
                 * aufgerufen. Der Parameter pageIndex gibt jeweils an, welche
                 * Seite gedruckt werden soll. Dementsprechend wird der
                 * Grafikbereich gewählt, der ausgedruckt wird.
                 */
                public int print(Graphics g, PageFormat pf, int pageIndex) {
                    //Das Graphics-Objekt wird in ein Graphics2D-Objekt gecastet
                    //um auf dessen Methoden zugreifen zu können.
                    Graphics2D g2 = (Graphics2D)g;
                    //Schriftfarbe schwarz
                    g2.setColor(Color.black);

                    //DoubleBuffering in der EditorPane ausschalten
                    textEditorPane.setDoubleBuffered(false);

                    //Jetzt wird die Größe des Druckbereichs berechnet.
                    //Zunächst werden die Maße des Dokuments ermittelt (in Pixel)
                    Dimension d = textEditorPane.getSize();
                    double editorWidth  = d.width;
                    double editorHeight = d.height;

                    //Jetzt werden die Papiermaße ermittelt, die das Papierformat
                    //vorgibt
                    double pageWidth  = pf.getImageableWidth();
                    double pageHeight = pf.getImageableHeight();

                    //Da die Papiermaße und die EditorPane-Maße wohl selten
                    //übereinstimmen werden, ist ein Skalierungsfaktor notwendig
                    //Die Breite des Textes ist meist größer als die Papierbreite
                    double xScale = pageWidth/editorWidth;

                    //Über die Schriftgröße wird die Zeilenhöhe ermittelt. Damit
                    //keine Zeilen am unteren Rand abgeschnitten werden, muss
                    //eine max. Anzahl Zeilen pro Blatt berechnet werden. Daraus
                    //ergibt sich ein Skalierungsfaktor in der Y-Achse, der etwa
                    //zwischen 1.0 und 1.05 liegt.
                    double fontSize = (double)textEditorPane.getFont().getSize();
                    int linesPerPage = 1+(int)Math.ceil(pageHeight/fontSize);
                    //Die Zahl 4 ist ein experimentell ermittelter Wert, der
                    //eine minimale zusätzliche Korrektur bewirkt
                    double yScale = (4 + linesPerPage * fontSize) / pageHeight;

                    //Die Anzahl der Seiten für den Druck wird berechnet. Dabei
                    //wird der Skalierungsfaktor berücksichtigt, mit dem die
                    //Breite des Dokuments auf die Papierbreite angepasst wird.
                    int totalNumPages =
                        (int)Math.ceil(xScale*yScale*editorHeight / pageHeight);

                    //Da die Voreinstellung beim Druck gerne "Seite 1 - 9999"
                    //lautet, werden die vielen Leerseiten weggelassen.
                    if(pageIndex >= totalNumPages)
                        return NO_SUCH_PAGE;

                    // Der zu druckende Grafikbereich wird in die linke obere
                    //Ecke des Druckbereichs der Papierseite verschoben, damit
                    //werden die Seitenränder berücksichtigt
                    g2.translate(pf.getImageableX(), pf.getImageableY());
                    // Der jeweils aktuell zu zeichnende Grafikbereich wird an
                    //den oberen Rand verschoben -> Seitenzahl*Seitenhöhe
                    //Verschiebung also nur in Y-Richtung nach unten
                    g2.translate(0d, -pageIndex*pageHeight);
                    // Der auszugebende Grafikbereich wird so skaliert, dass er
                    //auf das Papier passt
                    g2.scale(xScale, xScale*yScale);
                    //Der Grafikbereich wird für den Druck neu gezeichnet
                    textEditorPane.paint(g2);
                    //DoubleBuffering in der EditorPane wieder einschalen
                    textEditorPane.setDoubleBuffered(true);
                    //Dem Druckjob mitteilen, dass eine Seite für den Druck
                    //existiert
                    return Printable.PAGE_EXISTS; //NO_SUCH_PAGE; //
                }
            });
            if(job.printDialog() == false)
                return;//kein Druck, da abgebrochen
            job.print();//sonst ausdrucken
        } catch(PrinterException e) {
            //Fehler in einem Nachrichtenfenster anzeigen
            JOptionPane.showMessageDialog(this, "Fehler beim Druck" + e,
                "Druckerfehler", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * Nach Auswahl einer Datei per Dateidialog wird diese von der JEditorPane
     * geladen.
     */
    private void showOpenFileDialog(java.awt.event.ActionEvent evt) {
        int pressedButton = fileChooser.showOpenDialog(this);
        if(pressedButton == JFileChooser.APPROVE_OPTION) {
            try {
                File f = fileChooser.getSelectedFile();
                this.fileName = f.getName();
                BufferedInputStream in = new BufferedInputStream(
                    new FileInputStream(f));
                textEditorPane.read(in, null);
                in.close();
            } catch(IOException e) {
                e.printStackTrace(System.out);
            }

        }
    }

    /** Beenden */
    private void exitForm(java.awt.event.WindowEvent evt) {
        System.exit(0);
    }

    public static void main(String args[]) {
        new TextPrinter().show();
    }


}
