package cli_fmw.utils.autocomplete;
import cli_fmw.utils.DefaultColors;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.*;
/**
* Класс текстового поля с автокомплитом, работает на основе модели комбобокса
* @author petr
*/
public class JTextFieldAutoComplete extends JTextField implements FocusListener, ActionListener {
private ArrayList<ListSelectionListener> listeners;//листенеры смены выделения
private ComboBoxModel dataModel;
private boolean isCaseSensitive;
private boolean isStrict;
private boolean editable;
private boolean selectOnFocusGained;//выделять ли весь текст при получении фокуса
private Map<String, Integer> indexMap;//мап строковому значению элемента ставит его индекс в модели данных
private Object selectedItem;
/**
* Хитрожопый документ на котором всё держится
*/
private class AutoDocument extends PlainDocument {
@Override
public void replace(int i, int j, String s, AttributeSet attributeset)
throws BadLocationException {
super.remove(i, j);
insertString(i, s, attributeset);
}
@Override
public void insertString(int i, String s, AttributeSet attributeset)
throws BadLocationException {
if (s == null || "".equals(s)) {
return;
}
int len = getLength();
String s1 = getText(0, i);
String sEnd = getText(i, len-i);
String s2 = getMatch(s1 + s);
int j = (i + s.length()) - 1;
if (isStrict && s2 == null) {
s2 = getMatch(s1);
j--;
} else if (!isStrict && s2 == null) {
super.insertString(i, s, attributeset);
return;
}
boolean newString = false;
if (s2.equals(s1+s)){
newString = true;
s2+=sEnd;
}
fireSelecton(s2);
super.remove(0, getLength());
super.insertString(0, s2, attributeset);
setSelectionStart(j + 1);
if (newString){
setSelectionEnd(j + 1);
}else{
setSelectionEnd(getLength());
}
// setCaretPosition(j);
}
@Override
public void remove(int i, int j) throws BadLocationException {
int k = getSelectionStart();
if (k > 0) {
k--;
}
String s = getMatch(getText(0, k));
if (!isStrict && s == null) {
super.remove(i, j);
} else {
super.remove(0, getLength());
super.insertString(0, s, null);
}
fireSelecton(s);
try {
setSelectionStart(k);
setSelectionEnd(getLength());
} catch (Exception exception) {
exception.printStackTrace();
}
// setCaretPosition(k);
}
private void fireSelecton(String s) {
if (s != null) {
Integer number = indexMap.get(s);
if (number != null && dataModel.getElementAt(number) != null) {
fireChangeSelection(dataModel.getElementAt(number), number);
} else if (editable) {
fireChangeSelection(s, -1);
}
}
}
}
public JTextFieldAutoComplete() {
this(new DefaultComboBoxModel());
}
public JTextFieldAutoComplete(Object[] items) {
this(new DefaultComboBoxModel(items));
}
public JTextFieldAutoComplete(ComboBoxModel dataModel) {
isCaseSensitive = false;
isStrict = true;
selectOnFocusGained = true;
addFocusListener(this);
addActionListener(this);
if (dataModel == null) {
throw new IllegalArgumentException("values can not be null");
} else {
setModel(dataModel);
init();
return;
}
}
private void init() {
AutoDocument autoDocument = new AutoDocument();
setDocument(autoDocument);
if (isStrict && dataModel.getSize() > 0) {
setText(dataModel.getElementAt(0).toString());
}
}
/**
* поиск элемента в модели данных начало строкового значения которого совпадает
* с входной строкой
* так же происходит обработка класса <code>HasColors</code>
* определяется цвет текста и фона
* @param s входная строка
* @return найденная строка (если найдена) или входная строка (если других нет)
*/
private String getMatch(String s) {
for (int i = 0; i < dataModel.getSize(); i++) {
Object element = dataModel.getElementAt(i);
String s1 = element.toString();
if (s1 != null) {
setBackground(DefaultColors.TEXT_FIELD_BACKGROUND);
setForeground(DefaultColors.TEXT);
if (!isCaseSensitive && s1.toLowerCase().startsWith(s.toLowerCase())) {
if (element instanceof HasColors){
HasColors hc = (HasColors) element;
if (hc.getBackground() != null){
setBackground(hc.getBackground());
}
if (hc.getForeground() != null){
setForeground(hc.getForeground());
}
}
return s1;
}
if (isCaseSensitive && s1.startsWith(s)) {
if (element instanceof HasColors){
HasColors hc = (HasColors) element;
if (hc.getBackground() != null){
setBackground(hc.getBackground());
}
if (hc.getForeground() != null){
setForeground(hc.getForeground());
}
}
return s1;
}
}
}
return s;
}
/**
* выделяет часть текста которая вписана автокомплитером
* (Внимание: иногда глючит)
* @param s
*/
@Override
public void replaceSelection(String s) {
AutoDocument _lb = (AutoDocument) getDocument();
if (_lb != null) {
try {
int i = Math.min(getCaret().getDot(), getCaret().getMark());
int j = Math.max(getCaret().getDot(), getCaret().getMark());
_lb.replace(i, j - i, s, null);
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
/**
* Проверяет чувствительность к регистру символов
* @return истина если различает
*/
public boolean isCaseSensitive() {
return isCaseSensitive;
}
/**
* Устанавливает чувствительность к регистру символов
* @param flag
*/
public void setCaseSensitive(boolean flag) {
isCaseSensitive = flag;
}
public boolean isStrict() {
return isStrict;
}
public void setStrict(boolean flag) {
isStrict = flag;
}
/**
* Вернёт используемую модель
* (не стоит менять модель через данную ссылку, т.к такая модель не будет проиндексирована)
* @return
*/
public ComboBoxModel getDataList() {
return dataModel;
}
/**
* Устанавливает и индексирует модель
* @param dataModel
*/
public void setModel(ComboBoxModel dataModel) {
if (dataModel == null) {
throw new IllegalArgumentException("values can not be null");
} else {
//идексация элементов (занесение в мап текст - номер)
this.dataModel = dataModel;
indexMap = new HashMap<String, Integer>();
for (int i = 0; i < dataModel.getSize(); i++) {
Object object = dataModel.getElementAt(i);
if (object != null) {
indexMap.put(object.toString(), i);
} else {
//??
}
}
return;
}
}
// public void setDataList(List list) {
// if (list == null) {
// throw new IllegalArgumentException("values can not be null");
// } else {
// dataList = new ArrayList(list);
// dataToList = new HashMap<String, Integer>();
// for (int i = 0; i < list.size(); i++) {
// Object object = list.get(i);
// if (object != null) {
// dataToList.put(object.toString(), i);
// } else {
// //??
// }
// }
//
// return;
// }
// }
/**
* Добавляет слушателя смены выделения
* @param listener
* @return
*/
public final ListSelectionListener addSelectionListener(ListSelectionListener listener){
if (listeners == null){
listeners = new ArrayList<ListSelectionListener>();
}
listeners.add(listener);
return listener;
}
/**
* Удаляет слушателя смены выделения
* @param listener
*/
public final void removeSelectionListener(ListSelectionListener listener){
if (listeners != null){
listeners.remove(listener);
}
}
/**
* Пинает листенеров
* @param selectedItem новый воделенный элемент
* @param number его номер в списке
*/
protected final void fireChangeSelection(Object selectedItem, int number){
this.selectedItem = selectedItem;
if (listeners != null){
for (ListSelectionListener listener : listeners) {
listener.valueChanged(new ListSelectionEvent(selectedItem, number, number, false));
}
}
}
@Override
public boolean isEditable() {
return editable;
}
@Override
public void setEditable(boolean b) {
editable = b;
}
public boolean isSelectOnFocusGained() {
return selectOnFocusGained;
}
public void setSelectOnFocusGained(boolean selectOnFocusGained) {
this.selectOnFocusGained = selectOnFocusGained;
}
@Override
public void focusGained(FocusEvent e) {
if (selectOnFocusGained){
selectAll();
}
}
@Override
public void focusLost(FocusEvent e) {
select(getText().length()-1, getText().length()-1);
fireActionPerformed();
}
/**
* по акшн перформед снятие выделения
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
select(getText().length(), getText().length());
}
public Object getSelectedItem(){
return selectedItem;
}
}