JPopupTextComponent.java

/*******************************************************************************
 * Copyhacked (H) 2012-2020.
 * This program and the accompanying materials
 * are made available under no term at all, use it like
 * you want, but share and discuss about it
 * every time possible with every body.
 *
 * Contributors:
 *      ron190 at ymail dot com - initial implementation
 *******************************************************************************/
package com.jsql.view.swing.text;

import com.jsql.util.LogLevelUtil;
import com.jsql.view.swing.popupmenu.JPopupMenuText;
import com.jsql.view.swing.text.action.SilentDeleteTextAction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.*;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import java.awt.event.ActionEvent;

/**
 * A swing JTextComponent with Undo/Redo functionality.
 * @param <T> Component like JTextField or JTextArea to decorate
 */
public class JPopupTextComponent<T extends JTextComponent> extends JPopupComponent<T> implements DecoratorJComponent<T> {
    
    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();

    /**
     * Save the component to decorate, add the Undo/Redo.
     * @param proxy Swing component to decorate
     */
    public JPopupTextComponent(final T proxy) {
        
        super(proxy);

        this.getProxy().setComponentPopupMenu(new JPopupMenuText(this.getProxy()));
        this.getProxy().setDragEnabled(true);

        var undoRedoManager = new UndoManager();
        var doc = this.getProxy().getDocument();

        // Listen for undo and redo events
        doc.addUndoableEditListener(undoableEditEvent -> undoRedoManager.addEdit(undoableEditEvent.getEdit()));

        this.initializeUndo(undoRedoManager);
        this.initializeRedo(undoRedoManager);
        this.makeDeleteSilent();
    }

    private void initializeUndo(final UndoManager undo) {
        
        final var undoIdentifier = "Undo";  // Create an undo action and add it to the text component
        
        this.getProxy().getActionMap().put(undoIdentifier, new AbstractAction(undoIdentifier) {
            @Override
            public void actionPerformed(ActionEvent evt) {
                // Unhandled ArrayIndexOutOfBoundsException #92146 on undo()
                try {
                    if (undo.canUndo()) {
                        undo.undo();
                    }
                } catch (ArrayIndexOutOfBoundsException | CannotUndoException e) {
                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
                }
            }
       });

        // Bind the undo action to ctl-Z
        this.getProxy().getInputMap().put(KeyStroke.getKeyStroke("control Z"), undoIdentifier);
    }

    private void initializeRedo(final UndoManager undo) {
        
        final var redoIdentifier = "Redo";  // Create a redo action and add it to the text component
        
        this.getProxy().getActionMap().put(redoIdentifier, new AbstractAction(redoIdentifier) {
            @Override
            public void actionPerformed(ActionEvent evt) {
                try {
                    if (undo.canRedo()) {
                        undo.redo();
                    }
                } catch (CannotRedoException e) {
                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
                }
            }
        });

        // Bind the redo action to ctl-Y
        this.getProxy().getInputMap().put(KeyStroke.getKeyStroke("control Y"), redoIdentifier);
    }

    private void makeDeleteSilent() {
        
        var actionMap = this.getProxy().getActionMap();  // Silent delete

        String key = DefaultEditorKit.deletePrevCharAction;
        actionMap.put(key, new SilentDeleteTextAction(key, actionMap.get(key)));

        key = DefaultEditorKit.deleteNextCharAction;
        actionMap.put(key, new SilentDeleteTextAction(key, actionMap.get(key)));
    }
}