View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    *
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   *******************************************************************************/
11  package com.jsql.view.swing.text;
12  
13  import com.jsql.util.LogLevelUtil;
14  import com.jsql.view.swing.popupmenu.JPopupMenuText;
15  import com.jsql.view.swing.text.action.SilentDeleteTextAction;
16  import org.apache.logging.log4j.LogManager;
17  import org.apache.logging.log4j.Logger;
18  
19  import javax.swing.*;
20  import javax.swing.text.DefaultEditorKit;
21  import javax.swing.text.JTextComponent;
22  import javax.swing.undo.CannotRedoException;
23  import javax.swing.undo.CannotUndoException;
24  import javax.swing.undo.UndoManager;
25  import java.awt.event.ActionEvent;
26  
27  /**
28   * A swing JTextComponent with Undo/Redo functionality.
29   * @param <T> Component like JTextField or JTextArea to decorate
30   */
31  public class JPopupTextComponent<T extends JTextComponent> extends JPopupComponent<T> implements DecoratorJComponent<T> {
32      
33      private static final Logger LOGGER = LogManager.getRootLogger();
34  
35      /**
36       * Save the component to decorate, add the Undo/Redo.
37       * @param proxy Swing component to decorate
38       */
39      public JPopupTextComponent(final T proxy) {
40          super(proxy);
41  
42          this.getProxy().setComponentPopupMenu(new JPopupMenuText(this.getProxy()));
43          this.getProxy().setDragEnabled(true);
44  
45          var undoRedoManager = new UndoManager();
46          var doc = this.getProxy().getDocument();
47  
48          // Listen for undo and redo events
49          doc.addUndoableEditListener(undoableEditEvent -> undoRedoManager.addEdit(undoableEditEvent.getEdit()));
50  
51          this.initUndo(undoRedoManager);
52          this.initRedo(undoRedoManager);
53          this.makeDeleteSilent();
54      }
55  
56      private void initUndo(final UndoManager undo) {
57          final var undoIdentifier = "Undo";  // Create an undo action and add it to the text component
58          
59          this.getProxy().getActionMap().put(undoIdentifier, new AbstractAction(undoIdentifier) {
60              @Override
61              public void actionPerformed(ActionEvent evt) {
62                  // Unhandled ArrayIndexOutOfBoundsException #92146 on undo()
63                  try {
64                      if (undo.canUndo()) {
65                          undo.undo();
66                      }
67                  } catch (ArrayIndexOutOfBoundsException | CannotUndoException e) {
68                      LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
69                  }
70              }
71         });
72  
73          // Bind the undo action to ctl-Z
74          this.getProxy().getInputMap().put(KeyStroke.getKeyStroke("control Z"), undoIdentifier);
75      }
76  
77      private void initRedo(final UndoManager undo) {
78          final var redoIdentifier = "Redo";  // Create a redo action and add it to the text component
79          
80          this.getProxy().getActionMap().put(redoIdentifier, new AbstractAction(redoIdentifier) {
81              @Override
82              public void actionPerformed(ActionEvent evt) {
83                  try {
84                      if (undo.canRedo()) {
85                          undo.redo();
86                      }
87                  } catch (CannotRedoException e) {
88                      LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
89                  }
90              }
91          });
92  
93          // Bind the redo action to ctl-Y
94          this.getProxy().getInputMap().put(KeyStroke.getKeyStroke("control Y"), redoIdentifier);
95      }
96  
97      private void makeDeleteSilent() {
98          var actionMap = this.getProxy().getActionMap();  // Silent delete
99  
100         String key = DefaultEditorKit.deletePrevCharAction;
101         actionMap.put(key, new SilentDeleteTextAction(key, actionMap.get(key)));
102 
103         key = DefaultEditorKit.deleteNextCharAction;
104         actionMap.put(key, new SilentDeleteTextAction(key, actionMap.get(key)));
105     }
106 }