diff --git a/src/simplejavatexteditor/AutoComplete.java b/src/simplejavatexteditor/AutoComplete.java
new file mode 100644
index 0000000..0f9c88b
--- /dev/null
+++ b/src/simplejavatexteditor/AutoComplete.java
@@ -0,0 +1,283 @@
+package simplejavatexteditor;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+
+/**
+ *
Auto complete functionality multiple programming languages, including brackets and
+ * parentheses
+ *
+ *
+ * An ArrayList is created for the keywords and the brackets.
+ * Logic for setting the content of the ArrayList is
+ * found in UI.java. If the word currently being typed
+ * matches a word in the list, a Runnable inner class is
+ * implemented to handle the word completion.
+ *
+ * Two other inner classes are also used. The second one handles when the enter
+ * key is pressed in response to an auto complete suggestion. The third one
+ * performs additional logic on brackets.
+ *
+ *
+ *
+ * @author Patrick Slagle
+ * @since 2016-12-03
+ */
+public class AutoComplete
+ implements DocumentListener {
+
+ private ArrayList brackets = new ArrayList<>();
+ private ArrayList bracketCompletions = new ArrayList<>();
+
+ private ArrayList words = new ArrayList<>();
+
+ SupportedKeywords kw;
+
+ //Keep track of when code completion
+ //has been activated
+ private enum Mode {
+
+ INSERT, COMPLETION
+ };
+
+ private final UI ui;
+ private Mode mode = Mode.INSERT;
+ private final JTextArea textArea;
+ private static final String COMMIT_ACTION = "commit";
+ private boolean isKeyword;
+ private int pos;
+ private String content;
+
+ public AutoComplete(UI ui, ArrayList al) {
+ //Set the keywords
+ words = al;
+ kw = new SupportedKeywords();
+ brackets = kw.getbrackets();
+ bracketCompletions = kw.getbracketCompletions();
+
+ //Access the editor
+ this.ui = ui;
+ textArea = ui.getEditor();
+
+ //Set the handler for the enter key
+ InputMap im = textArea.getInputMap();
+ ActionMap am = textArea.getActionMap();
+ im.put(KeyStroke.getKeyStroke("ENTER "), COMMIT_ACTION);
+ am.put(COMMIT_ACTION, new CommitAction());
+
+ Collections.sort(words);
+ }
+
+ /**
+ * A character has been typed into the document.
+ * This method performs the primary
+ * check to find a keyword completion.
+ *
+ * @param e
+ */
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ pos = e.getOffset();
+ content = null;
+
+ try {
+ content = textArea.getText(0, pos + 1);
+ } catch (BadLocationException ex) {
+ ex.printStackTrace();
+ }
+
+ if (e.getLength() != 1) {
+ return;
+ }
+
+ //Before checking for a keyword
+ checkForBracket();
+
+ //Get the beginning of the word being typed
+ int start;
+ for (start = pos; start >= 0; start--) {
+ if (!Character.isLetter(content.charAt(start))) {
+ break;
+ }
+ }
+
+ //Auto complete will start
+ //after two characters are typed
+ if (pos - start < 2) {
+ return;
+ }
+
+ //Search for a match on the word being typed
+ //in the keywords ArrayList
+ String prefix = content.substring(start + 1);
+ int n = Collections.binarySearch(words, prefix);
+
+ if (n < 0 && -n < words.size()) {
+ String match = words.get(-n - 1);
+
+ if (match.startsWith(prefix)) {
+ String completion = match.substring(pos - start);
+ isKeyword = true;
+ SwingUtilities.invokeLater(
+ new CompletionTask(completion, pos + 1));
+ } else {
+ mode = Mode.INSERT;
+ }
+ }
+ }
+
+ /**
+ * Performs a check to see if the last
+ * key typed was one of the supported
+ * bracket characters
+ */
+ private void checkForBracket() {
+ //String of the last typed character
+ char c = content.charAt(pos);
+ String s = String.valueOf(c);
+
+ for (int i = 0; i < brackets.size(); i++) {
+ if (brackets.get(i).equals(s)) {
+ isKeyword = false;
+ SwingUtilities.invokeLater(
+ new CompletionTask(bracketCompletions.get(i), pos + 1));
+ }
+ }
+ }
+
+ /**
+ * So that future classes can view the keyword list in the future.
+ *
+ * @return the keywords
+ */
+ private ArrayList getKeywords() {
+ return words;
+ }
+
+ /**
+ * So that these keywords can be modified or added to in the future.
+ *
+ * @param keyword the keyword to set
+ */
+ private void setKeywords(String keyword) {
+ words.add(keyword);
+ }
+
+ /**
+ * Handles the auto complete suggestion
+ * generated when the user is typing a
+ * word that matches a keyword.
+ */
+ private class CompletionTask
+ implements Runnable {
+
+ private final String completion;
+ private final int position;
+
+ public CompletionTask(String completion, int position) {
+ this.completion = completion;
+ this.position = position;
+ }
+
+ @Override
+ public void run() {
+ textArea.insert(completion, position);
+
+ textArea.setCaretPosition(position + completion.length());
+ textArea.moveCaretPosition(position);
+ mode = Mode.COMPLETION;
+ if (!isKeyword) {
+ textArea.addKeyListener(new HandleBracketEvent());
+ }
+ }
+ }
+
+ /**
+ * Enter key is pressed in response to an
+ * auto complete suggestion. Respond
+ * appropriately.
+ */
+ private class CommitAction
+ extends AbstractAction {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+
+ if (mode == Mode.COMPLETION) {
+ int pos = textArea.getSelectionEnd();
+
+ if (isKeyword) {
+ textArea.insert(" ", pos);
+ textArea.setCaretPosition(pos + 1);
+ mode = Mode.INSERT;
+ }
+ } else {
+ mode = Mode.INSERT;
+ textArea.replaceSelection("\n");
+ }
+ }
+ }
+
+ /**
+ * Additional logic for bracket auto complete
+ */
+ private class HandleBracketEvent
+ implements KeyListener {
+
+ @Override
+ public void keyTyped(KeyEvent e) {
+ //Bracket auto complete needs special attention.
+ //Multiple possible responses are needed.
+ String keyEvent = String.valueOf(e.getKeyChar());
+ for (String bracketCompletion : bracketCompletions) {
+ if (keyEvent.equals(bracketCompletion)) {
+ textArea.replaceRange("", pos, pos + 1);
+ mode = Mode.INSERT;
+ textArea.removeKeyListener(this);
+ }
+ }
+ int currentPosition = textArea.getCaretPosition();
+ switch (e.getKeyChar()) {
+ case '\n':
+ textArea.insert("\n\n", currentPosition);
+ textArea.setCaretPosition(currentPosition + 1);
+ mode = Mode.INSERT;
+ textArea.removeKeyListener(this);
+ break;
+ default:
+ textArea.setCaretPosition(pos);
+ mode = Mode.INSERT;
+ textArea.removeKeyListener(this);
+ break;
+ }
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ }
+}
\ No newline at end of file
diff --git a/src/simplejavatexteditor/SupportedKeywords.java b/src/simplejavatexteditor/SupportedKeywords.java
new file mode 100644
index 0000000..ef4e590
--- /dev/null
+++ b/src/simplejavatexteditor/SupportedKeywords.java
@@ -0,0 +1,62 @@
+package simplejavatexteditor;
+
+import java.util.ArrayList;
+
+/**
+ * A class to store the programming language keywords and
+ * provide access to them.
+ *
+ * Makes multiple language support possible and makes adding new language
+ * support convenient. To add more keywords, add a string array and getters
+ * to this class. Then, update the switch statement in UI.java.
+ */
+public class SupportedKeywords {
+ private String[] java = {"abstract", "assert", "boolean",
+ "break", "byte", "case", "catch", "char", "class", "const",
+ "continue", "default", "do", "double", "else", "extends", "false",
+ "final", "finally", "float", "for", "goto", "if", "implements",
+ "import", "instanceof", "int", "System", "out", "print()", "println()",
+ "new", "null", "package", "private", "protected", "public", "interface",
+ "long", "native", "return", "short", "static", "strictfp", "super", "switch",
+ "synchronized", "this", "throw", "throws", "transient", "true",
+ "try", "void", "volatile", "while", "String"};
+
+ private String[] cpp = { "auto", "const", "double", "float", "int", "short",
+ "struct", "unsigned", "break", "continue", "else", "for", "long", "signed",
+ "switch", "void", "case", "default", "enum", "goto", "register", "sizeof",
+ "typedef", "volatile", "char", "do", "extern", "if", "return", "static",
+ "union", "while", "asm", "dynamic_cast", "namespace", "reinterpret_cast", "try",
+ "bool", "explicit", "new", "static_cast", "typeid", "catch", "false", "operator",
+ "template", "typename", "class", "friend", "private", "this", "using", "const_cast",
+ "inline", "public", "throw", "virtual", "delete", "mutable", "protected", "true", "wchar_t" };
+
+ private String[] brackets = { "{", "(" };
+ private String[] bCompletions = { "}", ")" };
+ public String[] getJavaKeywords() {
+ return java;
+ }
+ public String[] getCppKeywords() {
+ return cpp;
+ }
+ public ArrayList getbracketCompletions() {
+ ArrayList al = new ArrayList<>();
+ for(String completion : bCompletions) {
+ al.add(completion);
+ }
+ return al;
+ }
+ public ArrayList getbrackets() {
+ ArrayList al = new ArrayList<>();
+ for(String completion : brackets) {
+ al.add(completion);
+ }
+ return al;
+ }
+ public ArrayList setKeywords(String[] arr) {
+ ArrayList al = new ArrayList<>();
+ for(String words : arr) {
+ al.add(words);
+ }
+ return al;
+ }
+}
\ No newline at end of file
diff --git a/src/simplejavatexteditor/UI.java b/src/simplejavatexteditor/UI.java
index 053932e..8e6d343 100644
--- a/src/simplejavatexteditor/UI.java
+++ b/src/simplejavatexteditor/UI.java
@@ -19,19 +19,26 @@
package simplejavatexteditor;
// GUI
+
import javax.swing.*;
-import java.awt.BorderLayout;
-import java.awt.Container;
-import java.awt.Font;
-import java.awt.event.*;
+import javax.swing.border.Border;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.Scanner;
+
// Input Stream
-import java.io.*;
// Various
-import java.util.Scanner;
-import javax.swing.border.Border;
public class UI extends JFrame implements ActionListener {
-
+
private static final long serialVersionUID = 1L;
private final Container container;
private final JTextArea textArea;
@@ -39,40 +46,50 @@ public class UI extends JFrame implements ActionListener {
private final JMenu menuFile, menuEdit, menuFind, menuAbout;
private final JMenuItem newFile, openFile, saveFile, close, clearFile, quickFind, aboutMe, aboutSoftware;
private final JToolBar mainToolbar;
- JButton newButton, openButton, saveButton, clearButton, quickButton, aboutMeButton, aboutButton, closeButton, spaceButton1, spaceButton2;
-
+ JButton newButton, openButton,
+ saveButton, clearButton,
+ quickButton, aboutMeButton,
+ aboutButton, closeButton,
+ spaceButton1, spaceButton2;
+
//setup icons - File Menu
private final ImageIcon newIcon = new ImageIcon("icons/new.png");
private final ImageIcon openIcon = new ImageIcon("icons/open.png");
private final ImageIcon saveIcon = new ImageIcon("icons/save.png");
private final ImageIcon closeIcon = new ImageIcon("icons/close.png");
-
+
//setup icons - Search Menu
private final ImageIcon clearIcon = new ImageIcon("icons/clear.png");
-
+
//setup icons - Search Menu
private final ImageIcon searchIcon = new ImageIcon("icons/search.png");
-
+
//setup icons - Help Menu
private final ImageIcon aboutMeIcon = new ImageIcon("icons/about_me.png");
private final ImageIcon aboutIcon = new ImageIcon("icons/about.png");
-
- public UI() {
+
+ AutoComplete autocomplete;
+ private boolean hasListener = false;
+
+ public UI() {
container = getContentPane();
-
+
// Set the initial size of the window
- setSize(700, 500);
-
+ setSize(700, 500);
+
// Set the title of the window
setTitle("Undefined | " + SimpleJavaTextEditor.NAME);
-
+
// Set the default close operation (exit when it gets closed)
- setDefaultCloseOperation(EXIT_ON_CLOSE);
-
+ setDefaultCloseOperation(EXIT_ON_CLOSE);
+
// Set a default font for the TextArea
textArea = new JTextArea("", 0,0);
- textArea.setFont(new Font("Century Gothic", Font.BOLD, 12));
-
+ textArea.setFont(new Font("Century Gothic", Font.BOLD, 12));
+ textArea.setTabSize(2);
+ textArea.setFont(new Font("Century Gothic", Font.BOLD, 12));
+ textArea.setTabSize(2);
+
// This is why we didn't have to worry about the size of the TextArea!
getContentPane().setLayout(new BorderLayout()); // the BorderLayout bit makes it fill it automatically
getContentPane().add(textArea);
@@ -82,7 +99,7 @@ public UI() {
menuEdit = new JMenu("Edit");
menuFind = new JMenu("Search");
menuAbout = new JMenu("About");
-
+
// Set the Items Menu
newFile = new JMenuItem("New", newIcon);
openFile = new JMenuItem("Open", openIcon);
@@ -92,31 +109,31 @@ public UI() {
quickFind = new JMenuItem("Quick", searchIcon);
aboutMe = new JMenuItem("About Me", aboutMeIcon);
aboutSoftware = new JMenuItem("About Software", aboutIcon);
-
+
// Set the Menu Bar into the our GUI
menuBar = new JMenuBar();
- menuBar.add(menuFile);
+ menuBar.add(menuFile);
menuBar.add(menuEdit);
menuBar.add(menuFind);
- menuBar.add(menuAbout);
+ menuBar.add(menuAbout);
this.setJMenuBar(menuBar);
-
+
// New File
newFile.addActionListener(this); // Adding an action listener (so we know when it's been clicked).
newFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK)); // Set a keyboard shortcut
menuFile.add(newFile); // Adding the file menu
-
+
// Open File
openFile.addActionListener(this);
- openFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
- menuFile.add(openFile);
-
+ openFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
+ menuFile.add(openFile);
+
// Save File
saveFile.addActionListener(this);
saveFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
menuFile.add(saveFile);
-
+
// Close File
/*
@@ -132,90 +149,95 @@ public UI() {
// Clear File (Code)
clearFile.addActionListener(this);
- clearFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_K, InputEvent.CTRL_MASK));
+ clearFile.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_K, InputEvent.CTRL_MASK));
menuEdit.add(clearFile);
-
+
// Find Word
quickFind.addActionListener(this);
quickFind.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK));
menuFind.add(quickFind);
-
+
// About Me
aboutMe.addActionListener(this);
aboutMe.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
menuAbout.add(aboutMe);
-
+
// About Software
aboutSoftware.addActionListener(this);
aboutSoftware.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
menuAbout.add(aboutSoftware);
-
+
mainToolbar = new JToolBar();
this.add(mainToolbar, BorderLayout.NORTH);
//used to create space between button groups
Border emptyBorder = BorderFactory.createEmptyBorder(0, 0, 0, 50);
-
+
newButton = new JButton(newIcon);
newButton.setToolTipText("New");
newButton.addActionListener(this);
mainToolbar.add(newButton);
mainToolbar.addSeparator();
-
+
openButton = new JButton(openIcon);
openButton.setToolTipText("Open");
openButton.addActionListener(this);
mainToolbar.add(openButton);
mainToolbar.addSeparator();
-
+
saveButton = new JButton(saveIcon);
saveButton.setToolTipText("Save");
saveButton.addActionListener(this);
mainToolbar.add(saveButton);
mainToolbar.addSeparator();
-
+
clearButton = new JButton(clearIcon);
clearButton.setToolTipText("Clear All");
clearButton.addActionListener(this);
mainToolbar.add(clearButton);
mainToolbar.addSeparator();
-
+
quickButton = new JButton(searchIcon);
quickButton.setToolTipText("Quick Search");
quickButton.addActionListener(this);
mainToolbar.add(quickButton);
-
+
//create space between button groups
spaceButton1 = new JButton();
spaceButton1.setBorder(emptyBorder);
mainToolbar.add(spaceButton1);
-
+
aboutMeButton = new JButton(aboutMeIcon);
aboutMeButton.setToolTipText("About Me");
aboutMeButton.addActionListener(this);
mainToolbar.add(aboutMeButton);
mainToolbar.addSeparator();
-
+
aboutButton = new JButton(aboutIcon);
aboutButton.setToolTipText("About NotePad PH");
aboutButton.addActionListener(this);
mainToolbar.add(aboutButton);
-
+
//create space between button groups
spaceButton2 = new JButton();
spaceButton2.setBorder(emptyBorder);
mainToolbar.add(spaceButton2);
-
+
closeButton = new JButton(closeIcon);
closeButton.setToolTipText("Close");
closeButton.addActionListener(this);
mainToolbar.add(closeButton);
}
-
+
+ //make the TextArea available to the autocomplete handler
+ protected JTextArea getEditor() {
+ return textArea;
+ }
+
public void actionPerformed (ActionEvent e) {
// If the source of the event was our "close" option
if(e.getSource() == close || e.getSource() == closeButton)
this.dispose(); // dispose all resources and close the application
-
+
// If the source was the "new" file option
else if(e.getSource() == newFile || e.getSource() == newButton) {
FEdit.clear(textArea);
@@ -246,7 +268,7 @@ else if(e.getSource() == openFile || e.getSource() == openButton) {
// If the source of the event was the "save" option
else if(e.getSource() == saveFile || e.getSource() == saveButton) {
// Open a file chooser
- JFileChooser fileChoose = new JFileChooser();
+ JFileChooser fileChoose = new JFileChooser();
// Open the file, only this time we call
int option = fileChoose.showSaveDialog(this);
@@ -262,16 +284,55 @@ else if(e.getSource() == saveFile || e.getSource() == saveButton) {
// Create a buffered writer to write to a file
BufferedWriter out = new BufferedWriter(new FileWriter(file.getPath()));
// Write the contents of the TextArea to the file
- out.write(textArea.getText());
+ out.write(textArea.getText());
// Close the file stream
- out.close();
+ out.close();
+
+ //If the user saves files with supported
+ //file types more than once, we need to remove
+ //previous listeners to avoid bugs.
+ if(hasListener) {
+ textArea.getDocument().removeDocumentListener(autocomplete);
+ hasListener = false;
+ }
+
+ //With the keywords located in a separate class,
+ //we can support multiple languages and not have to do
+ //much to add new ones.
+ SupportedKeywords kw = new SupportedKeywords();
+ ArrayList arrayList;
+ String[] list = { ".java", ".cpp" };
+
+ //Iterate through the list, find the supported
+ //file extension, apply the appropriate getter method from
+ //the keyword class
+ for(int i = 0; i < list.length; i++) {
+ if(file.getName().endsWith(list[i])) {
+ switch(i) {
+ case 0:
+ String[] jk = kw.getJavaKeywords();
+ arrayList = kw.setKeywords(jk);
+ autocomplete = new AutoComplete(this, arrayList);
+ textArea.getDocument().addDocumentListener(autocomplete);
+ hasListener = true;
+ break;
+ case 1:
+ String[] ck = kw.getCppKeywords();
+ arrayList = kw.setKeywords(ck);
+ autocomplete = new AutoComplete(this, arrayList);
+ textArea.getDocument().addDocumentListener(autocomplete);
+ hasListener = true;
+ break;
+ }
+ }
+ }
} catch (Exception ex) { // again, catch any exceptions and...
// ...write to the debug console
System.out.println(ex.getMessage());
}
}
}
-
+
// Clear File (Code)
if(e.getSource() == clearFile || e.getSource() == clearButton) {
FEdit.clear(textArea);
@@ -279,8 +340,8 @@ else if(e.getSource() == saveFile || e.getSource() == saveButton) {
// Find
if(e.getSource() == quickFind || e.getSource() == quickButton) {
new Find(textArea);
- }
-
+ }
+
// About Me
else if(e.getSource() == aboutMe || e.getSource() == aboutMeButton) {
new About().me();
@@ -289,7 +350,6 @@ else if(e.getSource() == aboutMe || e.getSource() == aboutMeButton) {
else if(e.getSource() == aboutSoftware || e.getSource() == aboutButton) {
new About().software();
}
-
+
}
-
-}
\ No newline at end of file
+}