So basically, the idea is as follows:
- The program must extend JTextPane
- The program must detect when user typed an abbreviation in the extended JTextPane.
- 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.
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;
}
}