Skip to content

Commit

Permalink
Completion, CompletionDocument refactored to support accented charact…
Browse files Browse the repository at this point in the history
…er input in Linux
  • Loading branch information
bjorndarri committed Nov 29, 2024
1 parent 3ba6665 commit 1a616e2
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 57 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Codion Change Log
- Completion.COMBO_BOX_COMPLETION_MODE renamed COMPLETION_MODE.
- ComboBoxBuilder.normalize() added.
- ItemComboBoxBuilder.normalize() added.
- Completion, CompletionDocument refactored to support accented character input in Linux.

## 0.18.21
### is.codion.common.db
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ dependencyResolutionManagement {

version("jfreechart", "1.5.5")
version("junit", "5.11.3")
version("assertj.swing", "4.0.0-beta-2")
version("sslcontext.kickstart", "8.3.7")
version("flatlaf", "3.5.2")
version("ikonli", "12.3.1")
Expand All @@ -213,6 +214,7 @@ dependencyResolutionManagement {
library("javalin-ssl", "io.javalin.community.ssl", "ssl-plugin").versionRef("javalin")

library("junit-api", "org.junit.jupiter", "junit-jupiter-api").versionRef("junit")
library("assertj-swing", "tokyo.northside", "assertj-swing-junit-jupiter").versionRef("assertj.swing")

library("sslcontext-kickstart", "io.github.hakky54", "sslcontext-kickstart").versionRef("sslcontext.kickstart")
library("flatlaf", "com.formdev", "flatlaf").versionRef("flatlaf")
Expand Down
1 change: 1 addition & 0 deletions swing/common-ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ dependencies {
api(libs.ikonli.swing)

testImplementation(libs.ikonli.foundation.pack)
testImplementation(libs.assertj.swing)
}
Original file line number Diff line number Diff line change
Expand Up @@ -241,31 +241,33 @@ private MaximumMatchDocument(JComboBox<?> comboBox, Normalize normalize) {
}

@Override
public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
int offs = offset;
if (selecting() || comboBoxModel().getSize() == 0) {
return;
}
super.insertString(offs, str, a);
boolean match = false;
Object item = lookupItem(getText(0, getLength()));
if (item != null) {
match = true;
setSelectedItem(item);
}
else {
item = comboBox().getSelectedItem();
offs = offs - str.length();
}

if (match) {
offs = maximumMatchingOffset(getText(0, getLength()), item);
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
if (selecting()) {
super.insertString(offs, str, a);
}
else {
offs += str.length();
trimSearchString(offs);
Object item = lookupItem(searchPattern(str));
boolean match = false;
if (item != null) {
searchString.insert(Math.min(searchString.length(), offs), str);
match = true;
setSelectedItem(item);
}
else {
item = comboBox().getSelectedItem();
offs = offs - str.length();
}
setTextAccordingToSelectedItem();
if (match) {
offs = maximumMatchingOffset(searchString.toString(), item);
searchString.replace(0, searchString.length(), getText(0, offs));
}
else {
offs += str.length();
}
highlightCompletedText(offs);
}
setTextAccordingToSelectedItem();
highlightCompletedText(offs);
}

// calculates how many characters are predetermined by the given pattern.
Expand Down Expand Up @@ -313,21 +315,23 @@ private AutoCompletionDocument(JComboBox<?> comboBox, Normalize normalize) {
}

@Override
public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
int offs = offset;
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
if (selecting()) {
return;
}
super.insertString(offs, str, a);
Object item = lookupItem(getText(0, getLength()));
if (item != null) {
setSelectedItem(item);
super.insertString(offs, str, a);
}
else {
offs = offs - str.length();
trimSearchString(offs);
Object item = lookupItem(searchPattern(str));
if (item != null) {
searchString.insert(offs, str);
setSelectedItem(item);
}
else {
offs = offs - str.length();
}
setTextAccordingToSelectedItem();
highlightCompletedText(offs + str.length());
}
setTextAccordingToSelectedItem();
highlightCompletedText(offs + str.length());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import javax.swing.ComboBoxEditor;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
Expand Down Expand Up @@ -50,6 +51,7 @@ class CompletionDocument extends PlainDocument {
private final JComboBox<?> comboBox;
private final ComboBoxModel<?> comboBoxModel;
private final boolean normalize;
protected final StringBuilder searchString = new StringBuilder();
// flag to indicate if setSelectedItem has been called
// subsequent calls to remove/insertString should be ignored
private boolean selecting = false;
Expand All @@ -71,16 +73,31 @@ protected CompletionDocument(JComboBox<?> comboBox, boolean normalize) {
}

@Override
public final void remove(int offset, int len) throws BadLocationException {
int offs = offset;
public void replace(int offset, int length, String string, AttributeSet attrs) throws BadLocationException {
if (selecting) {
return;
super.replace(offset, length, string, attrs);
}
if (hitBackspace) {
else if (string != null && string.length() > 0) {
if (length > 0) {
remove(offset, length);
}
insertString(offset, string, attrs);
}
}

@Override
public final void remove(int offs, int length) throws BadLocationException {
if (selecting) {
super.remove(offs, length);
}
else if (hitBackspace) {
hitBackspace = false;
boolean selectFirst = false;
// user hit backspace => move the selection backwards
// old item keeps being selected unless we've backspaced beyond the first character
if (searchString.length() > 0) {
searchString.replace(searchString.length() - 1, searchString.length(), "");
}
if (offs > 0) {
if (hitBackspaceOnSelection) {
offs--;
Expand All @@ -92,15 +109,12 @@ public final void remove(int offset, int len) throws BadLocationException {
else {
selectFirst = true;
}
if (selectFirst && comboBoxModel.getSize() > 0) {
if (selectFirst) {
setSelectedItem(comboBoxModel.getElementAt(0));
setTextAccordingToSelectedItem();
}
highlightCompletedText(offs);
}
else {
super.remove(offs, len);
}
}

protected final JComboBox<?> comboBox() {
Expand Down Expand Up @@ -132,44 +146,46 @@ protected final void setTextAccordingToSelectedItem() {
}
}

protected final void trimSearchString(int offset) {
searchString.replace(Math.min(searchString.length(), offset), searchString.length(), "");
}

protected final String searchPattern(String string) {
return new StringBuilder(searchString).insert(searchString.length(), string).toString();
}

protected final void highlightCompletedText(int start) {
editorComponent.setCaretPosition(getLength());
editorComponent.moveCaretPosition(start);
}

/**
* @param item Value to set for property 'selectedItem'.
*/
protected final void setSelectedItem(Object item) {
selecting = true;
comboBoxModel.setSelectedItem(item);
selecting = false;
}

protected final Object lookupItem(String pattern) {
protected final Object lookupItem(String startsWith) {
Object selectedItem = comboBoxModel.getSelectedItem();
// only search for a different item if the currently selected does not match
if (selectedItem != null && startsWithIgnoreCase(selectedItem.toString(), pattern, normalize)) {
if (selectedItem != null && startsWithIgnoreCase(selectedItem.toString(), startsWith, normalize)) {
return selectedItem;
}
else {
for (int i = 0; i < comboBoxModel.getSize(); i++) {
Object currentItem = comboBoxModel.getElementAt(i);
// current item starts with the pattern?
if (currentItem != null && startsWithIgnoreCase(currentItem.toString(), pattern, normalize)) {
return currentItem;
}
for (int i = 0; i < comboBoxModel.getSize(); i++) {
Object currentItem = comboBoxModel.getElementAt(i);
if (currentItem != null && startsWithIgnoreCase(currentItem.toString(), startsWith, normalize)) {
return currentItem;
}
}
// no item starts with the pattern => return null
return null;
}

protected static boolean startsWithIgnoreCase(String str1, String str2, boolean normalize) {
String one = normalize ? normalize(str1) : str1;
String two = normalize ? normalize(str2) : str2;
protected static boolean startsWithIgnoreCase(String string, String startsWith, boolean normalize) {
string = normalize ? normalize(string) : string;
startsWith = normalize ? normalize(startsWith) : startsWith;

return one.toUpperCase().startsWith(two.toUpperCase());
return string.toUpperCase().startsWith(startsWith.toUpperCase());
}

protected static String normalize(String string) {
Expand Down
Loading

0 comments on commit 1a616e2

Please sign in to comment.