From 831203f49156454ce21544c2bf35a4840f26743b Mon Sep 17 00:00:00 2001 From: Cary__Xx Date: Wed, 16 Oct 2019 23:59:02 +0800 Subject: [PATCH 1/3] Add autocomplete for user input --- src/main/java/seedu/address/MainApp.java | 2 +- .../java/seedu/address/ui/CommandBox.java | 91 ++++++++++++++ .../java/seedu/address/ui/ExpenseCard.java | 2 +- .../java/seedu/address/ui/MainWindow.java | 3 +- .../address/ui/autocomplete/AutoComplete.java | 107 ++++++++++++++++ .../ui/autocomplete/AutocompleteModel.java | 57 +++++++++ .../address/ui/autocomplete/BinarySearch.java | 99 +++++++++++++++ .../address/ui/autocomplete/QueryCard.java | 25 ++++ .../seedu/address/ui/autocomplete/Word.java | 114 ++++++++++++++++++ src/main/resources/view/CommandBox.fxml | 8 +- src/main/resources/view/DarkTheme.css | 22 ++-- src/main/resources/view/ExpenseCard.fxml | 35 ++++++ src/main/resources/view/ExpenseListCard.fxml | 48 -------- src/main/resources/view/MainWindow.fxml | 79 ++++++------ src/main/resources/view/QueryCard.fxml | 12 ++ 15 files changed, 600 insertions(+), 104 deletions(-) create mode 100644 src/main/java/seedu/address/ui/autocomplete/AutoComplete.java create mode 100644 src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java create mode 100644 src/main/java/seedu/address/ui/autocomplete/BinarySearch.java create mode 100644 src/main/java/seedu/address/ui/autocomplete/QueryCard.java create mode 100644 src/main/java/seedu/address/ui/autocomplete/Word.java create mode 100644 src/main/resources/view/ExpenseCard.fxml delete mode 100644 src/main/resources/view/ExpenseListCard.fxml create mode 100644 src/main/resources/view/QueryCard.fxml diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index af7a53a74ac..9f06b193f41 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 0, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 7d76e691f52..acfc9bd0893 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,12 +1,24 @@ package seedu.address.ui; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.event.EventHandler; import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.autocomplete.AutoComplete; +import seedu.address.ui.autocomplete.QueryCard; /** * The UI component that is responsible for receiving user command inputs. @@ -21,11 +33,72 @@ public class CommandBox extends UiPart { @FXML private TextField commandTextField; + @FXML + private ListView apSuggestions; + public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); + autoCompleteListener(); + } + + /** + * Handles the autofill event + */ + private void autoCompleteListener() { + commandTextField.textProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, + String oldValue, String newValue) { + String[] terms = newValue.split(" "); + String latestQuery = terms[terms.length - 1]; + ObservableList suggestions = + FXCollections.observableArrayList(new AutoComplete().getSuggestions(latestQuery)); + apSuggestions.setItems(suggestions); + apSuggestions.setCellFactory(listView -> new AutocompleteListViewCell()); + commandTextField.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent event) { + if (event.getCode() == KeyCode.DOWN) { + apSuggestions.requestFocus(); + apSuggestions.getSelectionModel().selectFirst(); + } + } + }); + apSuggestions.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent event) { + setTextField(latestQuery); + apSuggestions.getItems().clear(); + } + }); + apSuggestions.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent event) { + if (event.getCode() == KeyCode.ENTER) { + setTextField(latestQuery); + apSuggestions.getItems().clear(); + } + } + }); + Platform.runLater(new Runnable() { + @Override + public void run() { + commandTextField.positionCaret(commandTextField.getLength()); + } + }); + } + }); + } + + private void setTextField(String caretSelection) { + String currentText = commandTextField.getText(); + int index = commandTextField.getText().lastIndexOf(caretSelection); + String sub = currentText.substring(0, index); + commandTextField.setText(sub + apSuggestions.getSelectionModel().getSelectedItem()); + commandTextField.requestFocus(); } /** @@ -66,6 +139,7 @@ private void setStyleToIndicateCommandFailure() { */ @FunctionalInterface public interface CommandExecutor { + /** * Executes the command and returns the result. * @@ -74,4 +148,21 @@ public interface CommandExecutor { CommandResult execute(String commandText) throws CommandException, ParseException; } + /** + * Custom {@code ListCell} that displays the graphics of a {@code Query} using a {@code QueryCard}. + */ + class AutocompleteListViewCell extends ListCell { + + @Override + protected void updateItem(String query, boolean empty) { + super.updateItem(query, empty); + + if (empty || query == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new QueryCard(query).getRoot()); + } + } + } } diff --git a/src/main/java/seedu/address/ui/ExpenseCard.java b/src/main/java/seedu/address/ui/ExpenseCard.java index 69245810d94..33aceaf0cac 100644 --- a/src/main/java/seedu/address/ui/ExpenseCard.java +++ b/src/main/java/seedu/address/ui/ExpenseCard.java @@ -14,7 +14,7 @@ */ public class ExpenseCard extends UiPart { - private static final String FXML = "ExpenseListCard.fxml"; + private static final String FXML = "ExpenseCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 14945223f9f..530d1db4633 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -75,6 +75,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -154,7 +155,7 @@ void show() { @FXML private void handleExit() { GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); + (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); primaryStage.hide(); diff --git a/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java b/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java new file mode 100644 index 00000000000..be1b45a5df7 --- /dev/null +++ b/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java @@ -0,0 +1,107 @@ +package seedu.address.ui.autocomplete; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + */ +public class AutoComplete { + + private final int numOfSug = 10; + + private Word[] matchedResults; + private boolean displayWeights; + private AutocompleteModel apModel; + + public AutoComplete() { + matchedResults = new Word[numOfSug]; + displayWeights = false; + } + + /** + * @param filePath path for the library of words + * @return the suggestions to be shown in textField + */ + private static Word[] readWordsFromFile(String filePath) { + Word[] queries = null; + try { + BufferedReader reader0 = new BufferedReader(new FileReader(filePath)); + int lines = 0; + while (reader0.readLine() != null) { + lines++; + } + reader0.close(); + + BufferedReader reader1 = new BufferedReader(new FileReader(filePath)); + queries = new Word[lines]; + for (int i = 0; i < lines; i++) { + String line = reader1.readLine(); + if (line == null) { + System.err.println("Could not read line " + (i + 1) + " of " + filePath); + System.exit(1); + } + int tab = line.indexOf('\t'); + if (tab == -1) { + System.err.println("No tab character in line " + (i + 1) + " of " + filePath); + System.exit(1); + } + long weight = Long.parseLong(line.substring(0, tab).trim()); + String query = line.substring(tab + 1); + queries[i] = new Word(query, weight); + } + reader1.close(); + } catch (Exception e) { + System.err.println("Could not read or parse input file " + filePath); + e.printStackTrace(); + System.exit(1); + } + + return queries; + } + + /** + * Initialize an autocomplete model + */ + private void initAP() { + Word[] data = readWordsFromFile("/Users/apple/Downloads/autocomplete.txt"); + // Create the autocomplete object + apModel = new AutocompleteModel(data); + } + + /** + * Makes a call to the implementation of AutocompleteModel to get + * suggestions for the currently entered text. + * + * @param text string to search for + */ + public List getSuggestions(String text) { + + initAP(); + List suggestions = new ArrayList<>(); + // don't search for suggestions if there is no input + if (text.length() > 0) { + // get all matching words + Word[] allResults = apModel.allMatches(text); + if (allResults == null) { + throw new NullPointerException("allMatches() is null"); + } + + if (allResults.length > numOfSug) { + matchedResults = Arrays.copyOfRange(allResults, 0, numOfSug); + } else { + matchedResults = allResults; + } + + if (!displayWeights) { + for (Word matchedResult : matchedResults) { + suggestions.add(matchedResult.getQuery()); + } + } + } + return suggestions; + } +} diff --git a/src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java b/src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java new file mode 100644 index 00000000000..a4da8e9520d --- /dev/null +++ b/src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java @@ -0,0 +1,57 @@ +package seedu.address.ui.autocomplete; + +import java.util.Arrays; + +/** + * Given the data and the query, this class is for searching for words in the data + * starting with the given query and returns the array in descending order w.r.t weight. + */ +public class AutocompleteModel { + + private final Word[] dataCopy; + + /** + * Initialize an AutocompleteModel using array of words. + * + * @param data - the array of queries + * @throws NullPointerException - if queries == null + */ + public AutocompleteModel(Word[] data) { + if (data == null) { + throw new NullPointerException("Data cannot be null"); + } + dataCopy = Arrays.copyOf(data, data.length); + Arrays.sort(dataCopy); + } + + /** + * Return all words that start with the given query, in descending order of weight. + * Query should be non-null. + * + * @param query - query to be searched for + * @return - array of words that match the given query in descending order + * @throws NullPointerException - if query == null + */ + public Word[] allMatches(String query) { + if (query == null) { + throw new NullPointerException("Query cannot be null"); + } + Word queryWord = new Word(query, 100); + int firstIndex = BinarySearch.firstIndexOf(dataCopy, queryWord, Word.compareCharSeq(query.length())); + + // NOT FOUND + if (firstIndex == -1) { + return new Word[0]; + } + int lastIndex = BinarySearch.lastIndexOf(dataCopy, queryWord, Word.compareCharSeq(query.length())); + + int range = lastIndex - firstIndex + 1; + Word[] allMatches = new Word[range]; + int j = 0; + for (int i = firstIndex; i <= lastIndex; i++) { + allMatches[j++] = dataCopy[i]; + } + Arrays.sort(allMatches, Word.compareWeightDescending()); + return allMatches; + } +} diff --git a/src/main/java/seedu/address/ui/autocomplete/BinarySearch.java b/src/main/java/seedu/address/ui/autocomplete/BinarySearch.java new file mode 100644 index 00000000000..78e12ee73cd --- /dev/null +++ b/src/main/java/seedu/address/ui/autocomplete/BinarySearch.java @@ -0,0 +1,99 @@ +package seedu.address.ui.autocomplete; + +import java.util.Comparator; + +/** + * Utility class to find the first and the last index of key using Binary Search. + */ +public class BinarySearch { + + /** + * Searches the specified array for the specified value using modification of binary + * search algorithm and returns the index of the first key in list[] that equals the search key, + * or -1 if no such key were found. + * + * @param list - the array of keys to be searched + * @param key - the value to be searched for + * @param comparator - the comparator by which array is ordered + * @return - the index of the last key in list that equals the search key, -1 if not found + * @throws NullPointerException - if list is null + * @throws NullPointerException - if key is null + * @throws NullPointerException - if comparator is null + */ + public static int firstIndexOf(T[] list, T key, Comparator comparator) { + if (list == null || key == null || comparator == null) { + throw new IllegalArgumentException(); + } + + if (list.length == 0) { + return -1; + } + if (comparator.compare(list[0], key) == 0) { + return 0; + } + + int lo = 0; + int hi = list.length - 1; + + while (lo <= hi) { + int mid = lo + (hi - lo) / 2; + int cmp = comparator.compare(list[mid], key); + + if (cmp >= 1) { + hi = mid - 1; + } else if (cmp <= -1) { + lo = mid + 1; + } else if (comparator.compare(list[mid - 1], list[mid]) == 0) { + hi = mid - 1; + } else { + return mid; + } + } + return -1; + } + + /** + * Searches the specified array for the specified value using modification of binary + * search algorithm and returns the index of the last key in list[] that equals the search key, + * or -1 if no such key were found. + * + * @param list - the array of keys to be searched + * @param key - the value to be searched for + * @param comparator - the comparator by which array is ordered + * @return - the index of the last key in list that equals the search key, -1 if not found + * @throws NullPointerException - if list is null + * @throws NullPointerException - if key is null + * @throws NullPointerException - if comparator is null + */ + public static int lastIndexOf(T[] list, T key, Comparator comparator) { + if (list == null || key == null || comparator == null) { + throw new IllegalArgumentException(); + } + + if (list.length == 0) { + return -1; + } + if (comparator.compare(list[list.length - 1], key) == 0) { + return list.length - 1; + } + + int lo = 0; + int hi = list.length - 1; + + while (lo <= hi) { + int mid = lo + (hi - lo) / 2; + int cmp = comparator.compare(list[mid], key); + + if (cmp >= 1) { + hi = mid - 1; + } else if (cmp <= -1) { + lo = mid + 1; + } else if (comparator.compare(list[mid + 1], list[mid]) == 0) { + lo = mid + 1; + } else { + return mid; + } + } + return -1; + } +} diff --git a/src/main/java/seedu/address/ui/autocomplete/QueryCard.java b/src/main/java/seedu/address/ui/autocomplete/QueryCard.java new file mode 100644 index 00000000000..1dab48d0b5c --- /dev/null +++ b/src/main/java/seedu/address/ui/autocomplete/QueryCard.java @@ -0,0 +1,25 @@ +package seedu.address.ui.autocomplete; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Query}. + */ +public class QueryCard extends UiPart { + + private static final String FXML = "QueryCard.fxml"; + + @FXML + private HBox apCard; + @FXML + private Label query; + + public QueryCard(String word) { + super(FXML); + query.setText(word); + } +} diff --git a/src/main/java/seedu/address/ui/autocomplete/Word.java b/src/main/java/seedu/address/ui/autocomplete/Word.java new file mode 100644 index 00000000000..04b3f4487f1 --- /dev/null +++ b/src/main/java/seedu/address/ui/autocomplete/Word.java @@ -0,0 +1,114 @@ +package seedu.address.ui.autocomplete; + +import java.util.Comparator; + +/** + * Word is an immutable data type that represents an autocomplete object. + * A query string and an associated integer weight(optional) + * Represents single search word with the query and the weight. + */ +public class Word implements Comparable { + + private final String query; + private final long weight; + + public Word() { + this.query = null; + this.weight = -1; + } + + /** + * Initializes a word with the specified query and the weight. + * Word should be non-null and weight must be non-negative. + * + * @param query - the query to be searched for + * @param weight - the corresponding weight of the query + * @throws NullPointerException - if query == null + * @throws IllegalArgumentException - if weight < 0 + */ + public Word(String query, long weight) { + if (query == null) { + throw new NullPointerException("Word can't be null"); + } + if (weight < 0) { + throw new IllegalArgumentException("Weight must be non-negative"); + } + + this.query = query; + this.weight = weight; + } + + /** + * Returns comparator to compare words in lexicographic order using + * only the first r characters of each query. Parameter r should be non-negative. + * + * @return -1 if first r characters of this are less than the first r characters of that + * 0 if first r characters of this are equal to the first r characters of that + * 1 if first r characters of this are larger than to the first r characters of that + */ + public static Comparator compareCharSeq(int len) { + if (len < 0) { + throw new IllegalArgumentException("length must be non-negative, but was " + len); + } + return (t1, t2) -> { + String q1 = truncateTarget(t1.query, len); + String q2 = truncateTarget(t2.query, len); + return Integer.compare(q1.compareToIgnoreCase(q2), 0); + }; + } + + /** + * For comparing first R chars + * + * @param targetString Desc string to compare + * @param len + * @return + */ + private static String truncateTarget(String targetString, int len) { + final int endIndex = Math.min(targetString.length(), len); + return targetString.substring(0, endIndex); + } + + /** + * Returns comparator for comparing words using their corresponding weights. + */ + public static Comparator compareWeightDescending() { + return Comparator.comparingLong(Word::getWeight).reversed(); + } + + public long getWeight() { + return weight; + } + + public String getQuery() { + return query; + } + + /** + * Returns a string representation of this word in the following format: + * the weight, followed by a tab, followed by the query. + */ + @Override + public String toString() { + return weight + "\t" + query; + } + + /** + * Compares two words in lexicographic order by word. + * + * @return -1 if this is (less than) that + * 0 if this (is the same as) that + * 1 if this (is larger than) that + */ + @Override + public int compareTo(Word word) { + int cmp = this.query.toLowerCase().compareTo(word.query.toLowerCase()); + if (cmp <= -1) { + return -1; + } else if (cmp >= 1) { + return 1; + } else { + return 0; + } + } +} diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..3eea2fd9a7e 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,15 @@ + + - + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..d3d0e3af077 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -56,11 +56,7 @@ -fx-size: 35; -fx-border-width: 0 0 1 0; -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; + -fx-border-color: transparent transparent derive(-fx-base, 80%) transparent; -fx-border-insets: 0 10 1 0; } @@ -95,7 +91,7 @@ .list-cell { -fx-label-padding: 0 0 0 0; - -fx-graphic-text-gap : 0; + -fx-graphic-text-gap: 0; -fx-padding: 0 0 0 0; } @@ -108,7 +104,7 @@ } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: #000000; } .list-cell:filled:selected #cardPane { @@ -133,13 +129,13 @@ } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#1d1d1d, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; } .status-bar { @@ -229,8 +225,8 @@ } .button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-background-color: white; + -fx-text-fill: #1d1d1d; } .button:focused { diff --git a/src/main/resources/view/ExpenseCard.fxml b/src/main/resources/view/ExpenseCard.fxml new file mode 100644 index 00000000000..7f3c1cb1e8d --- /dev/null +++ b/src/main/resources/view/ExpenseCard.fxml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ExpenseListCard.fxml b/src/main/resources/view/ExpenseListCard.fxml deleted file mode 100644 index e39e22a2fdd..00000000000 --- a/src/main/resources/view/ExpenseListCard.fxml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 3df886fa07b..f6f54e54983 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -13,48 +13,49 @@ - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - + + + + diff --git a/src/main/resources/view/QueryCard.fxml b/src/main/resources/view/QueryCard.fxml new file mode 100644 index 00000000000..f31a0ac3d82 --- /dev/null +++ b/src/main/resources/view/QueryCard.fxml @@ -0,0 +1,12 @@ + + + + + + + + + + + From 3bf4b05c7124b7ea3ef2ab67efb587077a660ba6 Mon Sep 17 00:00:00 2001 From: Cary__Xx Date: Tue, 22 Oct 2019 00:49:20 +0800 Subject: [PATCH 2/3] Fix reading logic and caret selection issue --- src/main/java/seedu/address/MainApp.java | 2 +- .../java/seedu/address/ui/CommandBox.java | 27 ++++----- .../address/ui/autocomplete/AutoComplete.java | 55 ++++++++++--------- ...pleteModel.java => AutoCompleteModel.java} | 6 +- src/main/resources/data/vocabulary.txt | 47 ++++++++++++++++ src/main/resources/view/CommandBox.fxml | 2 +- test | 1 - 7 files changed, 95 insertions(+), 45 deletions(-) rename src/main/java/seedu/address/ui/autocomplete/{AutocompleteModel.java => AutoCompleteModel.java} (92%) create mode 100644 src/main/resources/data/vocabulary.txt delete mode 100644 test diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 9f06b193f41..0cb9e834301 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(1, 0, 1, true); + public static final Version VERSION = new Version(1, 0, 1, false); private static final Logger logger = LogsCenter.getLogger(MainApp.class); diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index acfc9bd0893..01db5ccb4e0 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -34,7 +34,7 @@ public class CommandBox extends UiPart { private TextField commandTextField; @FXML - private ListView apSuggestions; + private ListView acSuggestions; public CommandBox(CommandExecutor commandExecutor) { super(FXML); @@ -48,6 +48,7 @@ public CommandBox(CommandExecutor commandExecutor) { * Handles the autofill event */ private void autoCompleteListener() { + AutoComplete.initAc(); commandTextField.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, @@ -55,31 +56,31 @@ public void changed(ObservableValue observable, String[] terms = newValue.split(" "); String latestQuery = terms[terms.length - 1]; ObservableList suggestions = - FXCollections.observableArrayList(new AutoComplete().getSuggestions(latestQuery)); - apSuggestions.setItems(suggestions); - apSuggestions.setCellFactory(listView -> new AutocompleteListViewCell()); + FXCollections.observableArrayList(AutoComplete.getSuggestions(latestQuery)); + acSuggestions.setItems(suggestions); + acSuggestions.setCellFactory(listView -> new AutocompleteListViewCell()); commandTextField.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent event) { if (event.getCode() == KeyCode.DOWN) { - apSuggestions.requestFocus(); - apSuggestions.getSelectionModel().selectFirst(); + acSuggestions.requestFocus(); + acSuggestions.getSelectionModel().selectFirst(); } } }); - apSuggestions.setOnMouseClicked(new EventHandler() { + acSuggestions.setOnMouseClicked(new EventHandler() { @Override public void handle(MouseEvent event) { setTextField(latestQuery); - apSuggestions.getItems().clear(); + acSuggestions.getItems().clear(); } }); - apSuggestions.setOnKeyPressed(new EventHandler() { + acSuggestions.setOnKeyPressed(new EventHandler() { @Override public void handle(KeyEvent event) { if (event.getCode() == KeyCode.ENTER) { setTextField(latestQuery); - apSuggestions.getItems().clear(); + acSuggestions.getItems().clear(); } } }); @@ -95,10 +96,10 @@ public void run() { private void setTextField(String caretSelection) { String currentText = commandTextField.getText(); - int index = commandTextField.getText().lastIndexOf(caretSelection); - String sub = currentText.substring(0, index); - commandTextField.setText(sub + apSuggestions.getSelectionModel().getSelectedItem()); + String sub = currentText.substring(0, currentText.lastIndexOf(caretSelection)); + commandTextField.setText(sub + acSuggestions.getSelectionModel().getSelectedItem()); commandTextField.requestFocus(); + commandTextField.positionCaret(commandTextField.getLength()); } /** diff --git a/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java b/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java index be1b45a5df7..e302b6eb199 100644 --- a/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java +++ b/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java @@ -1,21 +1,24 @@ package seedu.address.ui.autocomplete; import java.io.BufferedReader; -import java.io.FileReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import seedu.address.MainApp; + /** - * + * Main controller class to execute the searching logic */ public class AutoComplete { - private final int numOfSug = 10; + private static final int numOfSug = 10; - private Word[] matchedResults; - private boolean displayWeights; - private AutocompleteModel apModel; + private static AutoCompleteModel acModel; + private static Word[] matchedResults; + private static boolean displayWeights; public AutoComplete() { matchedResults = new Word[numOfSug]; @@ -23,39 +26,40 @@ public AutoComplete() { } /** - * @param filePath path for the library of words + * Read word storage from txt + * + * @param is path for the library of words * @return the suggestions to be shown in textField */ - private static Word[] readWordsFromFile(String filePath) { + private static Word[] readWordsFromFile(InputStream is) { Word[] queries = null; try { - BufferedReader reader0 = new BufferedReader(new FileReader(filePath)); + is.mark(0); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); int lines = 0; - while (reader0.readLine() != null) { + while (reader.readLine() != null) { lines++; } - reader0.close(); - - BufferedReader reader1 = new BufferedReader(new FileReader(filePath)); + is.reset(); queries = new Word[lines]; for (int i = 0; i < lines; i++) { - String line = reader1.readLine(); + String line = reader.readLine(); if (line == null) { - System.err.println("Could not read line " + (i + 1) + " of " + filePath); + System.err.println("Could not read line " + (i + 1)); System.exit(1); } int tab = line.indexOf('\t'); if (tab == -1) { - System.err.println("No tab character in line " + (i + 1) + " of " + filePath); + System.err.println("No tab character in line " + (i + 1)); System.exit(1); } long weight = Long.parseLong(line.substring(0, tab).trim()); String query = line.substring(tab + 1); queries[i] = new Word(query, weight); } - reader1.close(); + reader.close(); } catch (Exception e) { - System.err.println("Could not read or parse input file " + filePath); + System.err.println("Could not read or parse input file "); e.printStackTrace(); System.exit(1); } @@ -66,26 +70,25 @@ private static Word[] readWordsFromFile(String filePath) { /** * Initialize an autocomplete model */ - private void initAP() { - Word[] data = readWordsFromFile("/Users/apple/Downloads/autocomplete.txt"); - // Create the autocomplete object - apModel = new AutocompleteModel(data); + public static void initAc() { + InputStream in = MainApp.class.getResourceAsStream("/data/vocabulary.txt"); + Word[] data = readWordsFromFile(in); + acModel = new AutoCompleteModel(data); } /** - * Makes a call to the implementation of AutocompleteModel to get + * Makes a call to the implementation of AutoCompleteModel to get * suggestions for the currently entered text. * * @param text string to search for */ - public List getSuggestions(String text) { + public static List getSuggestions(String text) { - initAP(); List suggestions = new ArrayList<>(); // don't search for suggestions if there is no input if (text.length() > 0) { // get all matching words - Word[] allResults = apModel.allMatches(text); + Word[] allResults = acModel.allMatches(text); if (allResults == null) { throw new NullPointerException("allMatches() is null"); } diff --git a/src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java b/src/main/java/seedu/address/ui/autocomplete/AutoCompleteModel.java similarity index 92% rename from src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java rename to src/main/java/seedu/address/ui/autocomplete/AutoCompleteModel.java index a4da8e9520d..ed11370e314 100644 --- a/src/main/java/seedu/address/ui/autocomplete/AutocompleteModel.java +++ b/src/main/java/seedu/address/ui/autocomplete/AutoCompleteModel.java @@ -6,17 +6,17 @@ * Given the data and the query, this class is for searching for words in the data * starting with the given query and returns the array in descending order w.r.t weight. */ -public class AutocompleteModel { +public class AutoCompleteModel { private final Word[] dataCopy; /** - * Initialize an AutocompleteModel using array of words. + * Initialize an AutoCompleteModel using array of words. * * @param data - the array of queries * @throws NullPointerException - if queries == null */ - public AutocompleteModel(Word[] data) { + public AutoCompleteModel(Word[] data) { if (data == null) { throw new NullPointerException("Data cannot be null"); } diff --git a/src/main/resources/data/vocabulary.txt b/src/main/resources/data/vocabulary.txt new file mode 100644 index 00000000000..fc4b64773c9 --- /dev/null +++ b/src/main/resources/data/vocabulary.txt @@ -0,0 +1,47 @@ +1000 add +1000 clear +1000 delete +1000 edit +1000 exit +1000 find +1000 help +1000 list +100 n/ +100 a/ +100 d/ +100 t/ +100 2019 +100 2018 +100 2017 +100 2016 +100 01 +100 02 +100 03 +100 04 +100 05 +100 06 +100 07 +100 08 +100 09 +100 10 +100 11 +100 12 +100 13 +100 14 +100 15 +100 16 +100 17 +100 18 +100 19 +100 20 +100 21 +100 22 +100 23 +100 24 +100 25 +100 26 +100 27 +100 28 +100 29 +100 30 +100 31 diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 3eea2fd9a7e..c94046165ea 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -8,7 +8,7 @@ - + diff --git a/test b/test deleted file mode 100644 index cb826b50446..00000000000 --- a/test +++ /dev/null @@ -1 +0,0 @@ -check test From af33fdfbaf7578b00423f42e9160e9332f8ababc Mon Sep 17 00:00:00 2001 From: Cary__Xx Date: Wed, 23 Oct 2019 00:14:59 +0800 Subject: [PATCH 3/3] Arrange --- .../{ui/autocomplete => logic/search}/AutoComplete.java | 4 +++- .../{ui/autocomplete => logic/search}/BinarySearch.java | 2 +- .../address/{ui => model}/autocomplete/AutoCompleteModel.java | 4 +++- .../java/seedu/address/{ui => model}/autocomplete/Word.java | 4 ++-- src/main/java/seedu/address/ui/CommandBox.java | 3 +-- .../java/seedu/address/ui/{autocomplete => }/QueryCard.java | 3 +-- 6 files changed, 11 insertions(+), 9 deletions(-) rename src/main/java/seedu/address/{ui/autocomplete => logic/search}/AutoComplete.java (95%) rename src/main/java/seedu/address/{ui/autocomplete => logic/search}/BinarySearch.java (98%) rename src/main/java/seedu/address/{ui => model}/autocomplete/AutoCompleteModel.java (95%) rename src/main/java/seedu/address/{ui => model}/autocomplete/Word.java (97%) rename src/main/java/seedu/address/ui/{autocomplete => }/QueryCard.java (86%) diff --git a/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java b/src/main/java/seedu/address/logic/search/AutoComplete.java similarity index 95% rename from src/main/java/seedu/address/ui/autocomplete/AutoComplete.java rename to src/main/java/seedu/address/logic/search/AutoComplete.java index e302b6eb199..57546e9ba71 100644 --- a/src/main/java/seedu/address/ui/autocomplete/AutoComplete.java +++ b/src/main/java/seedu/address/logic/search/AutoComplete.java @@ -1,4 +1,4 @@ -package seedu.address.ui.autocomplete; +package seedu.address.logic.search; import java.io.BufferedReader; import java.io.InputStream; @@ -8,6 +8,8 @@ import java.util.List; import seedu.address.MainApp; +import seedu.address.model.autocomplete.AutoCompleteModel; +import seedu.address.model.autocomplete.Word; /** * Main controller class to execute the searching logic diff --git a/src/main/java/seedu/address/ui/autocomplete/BinarySearch.java b/src/main/java/seedu/address/logic/search/BinarySearch.java similarity index 98% rename from src/main/java/seedu/address/ui/autocomplete/BinarySearch.java rename to src/main/java/seedu/address/logic/search/BinarySearch.java index 78e12ee73cd..5ab63beade0 100644 --- a/src/main/java/seedu/address/ui/autocomplete/BinarySearch.java +++ b/src/main/java/seedu/address/logic/search/BinarySearch.java @@ -1,4 +1,4 @@ -package seedu.address.ui.autocomplete; +package seedu.address.logic.search; import java.util.Comparator; diff --git a/src/main/java/seedu/address/ui/autocomplete/AutoCompleteModel.java b/src/main/java/seedu/address/model/autocomplete/AutoCompleteModel.java similarity index 95% rename from src/main/java/seedu/address/ui/autocomplete/AutoCompleteModel.java rename to src/main/java/seedu/address/model/autocomplete/AutoCompleteModel.java index ed11370e314..74faa2a2fa3 100644 --- a/src/main/java/seedu/address/ui/autocomplete/AutoCompleteModel.java +++ b/src/main/java/seedu/address/model/autocomplete/AutoCompleteModel.java @@ -1,7 +1,9 @@ -package seedu.address.ui.autocomplete; +package seedu.address.model.autocomplete; import java.util.Arrays; +import seedu.address.logic.search.BinarySearch; + /** * Given the data and the query, this class is for searching for words in the data * starting with the given query and returns the array in descending order w.r.t weight. diff --git a/src/main/java/seedu/address/ui/autocomplete/Word.java b/src/main/java/seedu/address/model/autocomplete/Word.java similarity index 97% rename from src/main/java/seedu/address/ui/autocomplete/Word.java rename to src/main/java/seedu/address/model/autocomplete/Word.java index 04b3f4487f1..38c3c5ccbea 100644 --- a/src/main/java/seedu/address/ui/autocomplete/Word.java +++ b/src/main/java/seedu/address/model/autocomplete/Word.java @@ -1,4 +1,4 @@ -package seedu.address.ui.autocomplete; +package seedu.address.model.autocomplete; import java.util.Comparator; @@ -94,7 +94,7 @@ public String toString() { } /** - * Compares two words in lexicographic order by word. + * Compares two words in lexicographic order. * * @return -1 if this is (less than) that * 0 if this (is the same as) that diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 01db5ccb4e0..6175e727d99 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -17,8 +17,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.ui.autocomplete.AutoComplete; -import seedu.address.ui.autocomplete.QueryCard; +import seedu.address.logic.search.AutoComplete; /** * The UI component that is responsible for receiving user command inputs. diff --git a/src/main/java/seedu/address/ui/autocomplete/QueryCard.java b/src/main/java/seedu/address/ui/QueryCard.java similarity index 86% rename from src/main/java/seedu/address/ui/autocomplete/QueryCard.java rename to src/main/java/seedu/address/ui/QueryCard.java index 1dab48d0b5c..d30cf099868 100644 --- a/src/main/java/seedu/address/ui/autocomplete/QueryCard.java +++ b/src/main/java/seedu/address/ui/QueryCard.java @@ -1,10 +1,9 @@ -package seedu.address.ui.autocomplete; +package seedu.address.ui; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.ui.UiPart; /** * An UI component that displays information of a {@code Query}.