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

de.chrlembeck.util.swing.ContiguousUpdateManager Maven / Gradle / Ivy

The newest version!
package de.chrlembeck.util.swing;

import javax.swing.event.DocumentEvent;
import javax.swing.event.UndoableEditEvent;
import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

/**
 * UpdateManager für Textkomponenten, der im Gegensatz zur Standardimplementierung mehrere zusammenhängende Änderungen
 * zu einer Undo-Aktion zusammenfassen kann. Zusammenhängende Änderungen sind dabei kontinuierliche Texteingaben bis zum
 * Abschluss einer Zeile. Einfügeoperationen von mehreren Zeichen, Zeilenumbrüche und Eingaben an nicht
 * zusammenhängenden Positionen führen jeweils zur Erstellung neuer Undo-Aktionen.
 * 
 * @author Christoph Lembeck
 */
public class ContiguousUpdateManager extends UndoManager {

    /**
     * Version number of the current class.
     * 
     * @see java.io.Serializable
     */
    private static final long serialVersionUID = 4833041236176074626L;

    /**
     * Textkomponente, für die das Undo-Management übernommen werden soll.
     */
    private JTextComponent textComponent;

    /**
     * Aktuelles Edit-Objekt, zu dem gegebenenfalls noch weitere Änderungen hinzugefügt werden sollen.
     */
    protected ContiguousEdit currentEdit;

    /**
     * Position des Cursors nach der letzten Änderung.
     */
    private int lastCaretPosition;

    /**
     * Länge des Textdokuments nach der letzten Änderung.
     */
    private int lastLength;

    /**
     * Erstellt einen neuen UpdateManager für die übergebene Textkomponenten und fügt den Manager direkt als Listener
     * für Änderungen hinzu.
     * 
     * @param textComponent
     *            Text-Komponente, für die das Undo-Management übernommen werden soll.
     */
    public ContiguousUpdateManager(final JTextComponent textComponent) {
        this.textComponent = textComponent;
        textComponent.getDocument().addUndoableEditListener(this);
    }

    /**
     * Prüft, ob das EditEvent zu einer bereits angefangenen Änderung hinzugefügt werden kann, oder ob es sich um eine
     * neue Änderung handelt. Bei zusammenhängengen Änderungen (also hintereinander eingegebenen Zeichen) werden die
     * Änderungen zu einer Undo-Aktion zusammengefasst. Zeilenwechsel, Einfügeoperationen oder Eingaben an nicht
     * zusammenhängenden Positionen erzeugen jeweils eine neue Undo-Aktion.
     * 
     * @param editEvent
     *            Event von der Text-Komponente.
     */
    @Override
    public void undoableEditHappened(final UndoableEditEvent editEvent) {
        final UndoableEdit edit = editEvent.getEdit();

        // Gibt es kein offenes Undo, erstellen wir ein neues
        if (currentEdit == null) {
            currentEdit = createNewEdit(edit);
            return;
        }

        final int caretPosition = textComponent.getCaretPosition();
        final int offsetChange = caretPosition - lastCaretPosition;
        final Document document = textComponent.getDocument();
        final int deltaLength = document.getLength() - lastLength;
        if (caretPosition == lastCaretPosition && edit instanceof DefaultDocumentEvent) {
            // Prüfen, ob es sich um eine Attribut-Änderung handelt
            final DefaultDocumentEvent defaultDocEvent = (DefaultDocumentEvent) edit;
            if (DocumentEvent.EventType.CHANGE.equals(defaultDocEvent.getType())) {
                currentEdit.addEdit(edit);
                return;
            }
        } else if (deltaLength == 1 && offsetChange == 1 && "\n".equals(getCharAt(document, lastCaretPosition))) {
            // Bei Eingabe eines Zeilenwechsels fängt ein neuer Edit an.
            currentEdit.end();
            currentEdit = createNewEdit(edit);
            return;
        } else if (offsetChange == deltaLength && Math.abs(offsetChange) == 1) {
            // Zusammenhängende Änderungen der Länge 1 werden zusammengefasst.
            currentEdit.addEdit(edit);
            lastCaretPosition = caretPosition;
            lastLength = document.getLength();
            return;
        } else {
            // Bei allen anderen Änderungen fangen wir ein neues Edit-Objekt an.
            currentEdit.end();
            currentEdit = createNewEdit(edit);
        }
    }

    /**
     * Erzeugt ein neues Undo-Objekt für eine neue Änderung.
     * 
     * @param edit
     *            Edit, wie er von der Textkomponente übergeben wurde.
     * @return ContiguousEdit-Objekt für die Zusammenfassung ggf. weiterer, zusammenhängender Änderungen.
     */
    private ContiguousEdit createNewEdit(final UndoableEdit edit) {
        lastCaretPosition = textComponent.getCaretPosition();
        lastLength = textComponent.getDocument().getLength();
        currentEdit = new ContiguousEdit(edit);
        addEdit(currentEdit);
        return currentEdit;
    }

    /**
     * Liest das Zeichen an Position {@code pos} aus dem Dokument aus.
     * 
     * @param document
     *            Dokument, aus dem gelesen werden soll.
     * @param pos
     *            Position des Zeichens, welches gelesen werden soll
     * @return Zeichen an der gewünschten Stelle oder null, falls die Position ungültig war.
     */
    private static String getCharAt(final Document document, final int pos) {
        try {
            return document.getText(pos, 1);
        } catch (final BadLocationException e) {
            return null;
        }
    }

    /**
     * Edit-Objekt für die Zusammenfassung mehrerer zusammenhängender Änderungen
     * 
     * @author Christoph Lembeck
     */
    private class ContiguousEdit extends CompoundEdit {

        /**
         * Version number of the current class.
         * 
         * @see java.io.Serializable
         */
        private static final long serialVersionUID = 1283278942832943727L;

        /**
         * Erstellt ein neues Objekt und fügt die übergebene Änderung als initiale Änderung hinzu.
         * 
         * @param edit
         *            Initiale Änderung für dieses Objekt.
         */
        public ContiguousEdit(final UndoableEdit edit) {
            addEdit(edit);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void undo() throws CannotUndoException {
            // bei einem Undo Beenden wir das aktuelle Edit.
            if (currentEdit != null) {
                currentEdit.end();
                currentEdit = null;
            }
            super.undo();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isInProgress() {
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy