0

So basically, the idea is as follows:

  1. The program must extend JTextPane
  2. The program must detect when user typed an abbreviation in the extended JTextPane.
  3. If the user keeps his caret at the last position of the abbreviation, and presses CTRL + ENTER, the program must replace that abbreviation with a definition.

My question is, how would that be done, would somebody be able to make a demo to demonstrate that? I am aware that keyListener is not the best approach here.

Update A few hours later: I managed to get it working finally. I will write down my working demo down below, I hope it will save you the headache that I had to go through while working on it.

GIF Demonstration: enter image description here


import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

/**
 * JTextPane with capability to:
 * -To keep the correct amount of tabs on new line when user types Enter
 * -Dynamically add abbreviations
 * -To replace abbreviation with its definition when user keeps caret at last letter of the abbreviation and taps CTRL + ENTER
 * -To set position of the caret for each abbreviation after it is replaced by its definition
 */
public class AbbreviationJTextPane extends JTextPane {
    
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        
        // Create abbreviation JTextPane
        AbbreviationJTextPane abbreviationPane = new AbbreviationJTextPane();
        
        // Add abbreviations
        abbreviationPane.addAbbreviation("sysout", "System.out.println();", 19); // 19 sets caret between parenthesis ( )
        abbreviationPane.addAbbreviation("/**", "/*\n * \n * \n * /", 6); // 6 sets caret on second line after *
        abbreviationPane.addAbbreviation("sob", "short of breath", 15);
        
        // Add abbreviation JTextPane to JFrame
        frame.add(abbreviationPane);
         
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setTitle("Demo on abbreviation with CTRL + ENTER capability for JTextPane");
        frame.setSize(720, 250);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

    }
    
    private AbbreviationList abbreviations = new AbbreviationList();

    public void addAbbreviation(String abbreviation, String definition, int setCaretPositionBack) {
        abbreviations.add(new AbbreviationParameter(setCaretPositionBack, abbreviation, definition));
    }

    public AbbreviationJTextPane() {
        // Add key detection functional
        InputMap im = getInputMap();
        ActionMap am = getActionMap();
        
        // Add ENTER Listener - Replaces the way ENTER key works - new line will keep the same amount of tabs as on previous line
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "onEnter");
        am.put("onEnter", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onEnter();
            }
        });

        // Add CTRL + ENTER Listener
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK), "onCTRL_ENTER");
        am.put("onCTRL_ENTER", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onCtrlEnter();
            }
        });
    }
    
    // ====================== LISTENERS
    
    /**
     * Overrides the way tapping Enter key functions.
     * New line "\n" will keep the same amount of tabs "\t" as on previous line
     */
    private void onEnter() {
        newLineWithTabs();
    }
    
    /**
     * Overrides the way tapping CTRL + ENTER functions
     */
    private void onCtrlEnter() {
        // The method below must be called like this to avoid bugs where expanding does
        // not work on new line
        expandAbbreviation(" ");
        expandAbbreviation("\n");
        expandAbbreviation("\t");
    }
    
    // ====================== FUNCTIONAL
    
    /**
     * Inserts a new line.
     * New line "\n" will keep the same amount of tabs "\t" as on previous line
     */
    private void newLineWithTabs() {
        try {
            // Get the entire text in the document
            String text = getDocument().getText(0, getCaretPosition());
            
            // Get the end index of the text
            int end = text.length();
            
            // Insert relevant amount of tabs on each new line:
            String tabs = getTabsFromLine(text, end);
            
            // Replace all new lines in definition with relevant amount of tabs
            String definition = "\n".replace("\n", "\n"+tabs);
            
            // Insert the definition into the document at the start index
            getDocument().insertString(end, definition, null);
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * This method is used to expand an abbreviation in the document based on the
     * given word.
     * 
     * @param lastIndexOf - a String representing the word that the abbreviation is
     *                    being searched from
     */
    private void expandAbbreviation(String lastIndexOf) {
        try {
            // Get the caret position in the document
            int caretPosition = getCaretPosition();
            Document doc = getDocument();

            // Get the entire text in the document
            String text = doc.getText(0, caretPosition);

            // Get the start index of the word that is being searched
            int start = text.lastIndexOf(lastIndexOf) + 1;

            // Get the end index of the text
            int end = text.length();

            // Get the word that is being searched
            String word = text.substring(start, end);

            // Check if the abbreviations list contains the word and the caret position is
            // at the end of the document
            if (abbreviations.containsAbbreviation(word) && caretPosition == end) {
                // Get the definition of the word from the abbreviations list
                AbbreviationParameter inputParameter = abbreviations.getObjectForAbbreviation(word);

                // If input parameter is null, this means that no such abbreviation exists in
                // the list
                if (inputParameter == null) {
                    return;
                }

                // Get definition from inputParameter
                String definition = inputParameter.definition;
                
                // Insert relevant amount of tabs on each new line:
                String tabs = getTabsFromLine(text, end);
                
                // Replace all new lines in definition with relevant amount of tabs
                definition = definition.replace("\n", "\n"+tabs);

                // Remove the word from the document
                doc.remove(start, end - start);

                // Insert the definition into the document at the start index
                doc.insertString(start, definition, null);

                // Set caret onto the appropriate index
                setCaretPosition(start + inputParameter.caretPosition);
            }
        } catch (BadLocationException e) {
            // No need to print anything as BadLocationException error will keep happening
            // e.printStackTrace();
        }
    }
    
    // ====================== UTILITY METHODS
    
    /**
     * Gets the tabs from line.
     *
     * @param text - the entire text in the document
     * @param end -  the end index of the text
     * @return the tabs from line, string will be "" if there are no tabs at all
     */
    private String getTabsFromLine(String text, int end) {
        // Count all tabs in the line where caret is at present
        int tabsCount = countCharacter(getLineAtIndex(text, end), '\t');
        
        // Create String containing the amount of tabs necessary
        StringBuilder tabs = new StringBuilder();
        for(int iTab = 0; iTab < tabsCount; iTab++) {
            tabs.append("\t");
        }
        
        return tabs.toString();
    }
    
    /**
     * Returns the full line of a given index in a string.
     *
     * @param input the input string
     * @param index the index in the input string
     * @return the full line of the given index
     */
    public static String getLineAtIndex(String input, int index) {
        // Find the start index of the line
        int start = input.lastIndexOf("\n", index) + 1;
        
        // Find the end index of the line
        int end = input.indexOf("\n", index);
        if (end == -1) {
            end = input.length();
        }
        
        // Return the substring from the start to the end of the line
        return input.substring(start, end);
    }
    
    /**
     * Count the number of a specified character in a string.
     *
     * @param s the input string
     * @param c the character to count
     * @return the number of occurrences of the character in the string
     */
    public static int countCharacter(String s, char c) {
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == c) {
                count++;
            }
        }
        return count;
    }
    
    /**
     * Count the number of occurrences of a specified string in another string.
     *
     * @param input the input string
     * @param target the string to count
     * @return the number of occurrences of the target string in the input string
     */
    public static int countString(String input, String target) {
        int count = 0;
        int index = 0;
        while ((index = input.indexOf(target, index)) != -1) {
            count++;
            index += target.length();
        }
        return count;
    }
}

/**
 * Constructor class for abbreviations, their definition, and expected caret
 * location after use
 * 
 */
class AbbreviationParameter {
    /**
     * This value is meant to indicate how many chars forward the caret should be
     * placed onto after definition is placed.
     */
    final int caretPosition;

    final String abbreviation;
    final String definition;

    public AbbreviationParameter(int caretPosition, String abbreviation, String definition) {
        this.caretPosition = caretPosition;
        this.abbreviation = abbreviation;
        this.definition = definition;
    }

    /**
     * Gets the definition.
     *
     * @return the definition
     */
    public String getDefinition() {
        return definition;
    }
}

/**
 * List with all abbreviations and their values
 */
class AbbreviationList extends ArrayList<AbbreviationParameter> {
    private static final long serialVersionUID = -7332763119043404932L;

    /**
     * Checks if list contains given abbreviation.
     *
     * @param abbreviation - the abbreviation
     * @return true, if successful
     */
    public boolean containsAbbreviation(String abbreviation) {
        ArrayList<AbbreviationParameter> list = this;

        for (AbbreviationParameter stringInputParameter : list) {
            if (stringInputParameter.abbreviation.equals(abbreviation)) {
                return true;
            }
        }

        return false;
    }

    
    /**
     * Gets the object for abbreviation.
     *
     * @param abbreviation - the abbreviation
     * @return the {@link #AbbreviationList} object for abbreviation given if match is found, will return null if no such abbreviation is found
     */
    public AbbreviationParameter getObjectForAbbreviation(String abbreviation) {
        ArrayList<AbbreviationParameter> list = this;

        for (AbbreviationParameter stringInputParameter : list) {
            if (stringInputParameter.abbreviation.equals(abbreviation)) {
                return stringInputParameter;
            }
        }

        return null;
    }
}
Corey
  • 94
  • 10
  • 2
    I would start by looking at [Using Text Components](https://docs.oracle.com/javase/tutorial/uiswing/components/text.html) and its sub categories, in particular [Concepts: About Documents](https://docs.oracle.com/javase/tutorial/uiswing/components/generaltext.html#document) and [Implementing a Document Filter](https://docs.oracle.com/javase/tutorial/uiswing/components/generaltext.html#filter) – MadProgrammer Feb 07 '23 at 23:35
  • 1
    To handle the "expansion" workflow, you'll need to start with [How to Use Key Bindings](https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) – MadProgrammer Feb 07 '23 at 23:41
  • 1
    And something like [this](https://stackoverflow.com/questions/31661877/java-using-keylistener-to-check-if-parenthesis-is-pressed/31665872#31665872) might help – MadProgrammer Feb 07 '23 at 23:57
  • @MadProgrammer thank you, I figured it out and made it working. Also, I wrote the working solution as an addendum to my question. Thank you. – Corey Feb 08 '23 at 01:24
  • Check out https://stackoverflow.com/questions/31661877/java-using-keylistener-to-check-if-parenthesis-is-pressed/31665872#31665872 which deals with something simular – MadProgrammer Feb 08 '23 at 01:30

0 Answers0