package org.basex.gui.layout; import static org.basex.gui.layout.BaseXKeys.*; import java.awt.Font; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import javax.swing.event.MouseInputAdapter; import org.basex.gui.dialog.Dialog; import org.basex.io.IOFile; import org.basex.util.Token; import org.basex.util.list.IntList; import org.basex.util.list.StringList; /** * Combination of text field and a list, communicating with each other. * List entries are automatically completed if they match the first characters * of the typed in text. Moreover, the cursor keys can be used to scroll * through the list, and list entries can be chosen with mouse clicks. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class BaseXList extends BaseXBack { /** Single choice. */ final boolean single; /** Text field. */ final BaseXTextField text; /** List. */ final JList list; /** List values. */ String[] values; /** Numeric list. */ boolean num = true; /** Scroll pane. */ private final JScrollPane scroll; /** * Default constructor. * @param choice the input values for the list * @param d dialog reference */ public BaseXList(final String[] choice, final Dialog d) { this(choice, d, true); } /** * Default constructor. * @param choice the input values for the list * @param d dialog reference * @param s only allow single choices */ public BaseXList(final String[] choice, final Dialog d, final boolean s) { // cache list values values = choice.clone(); single = s; // checks if list is purely numeric for(final String v : values) num = num && v.matches("[0-9]+"); layout(new TableLayout(2, 1)); text = new BaseXTextField(d); text.addFocusListener(new FocusAdapter() { @Override public void focusGained(final FocusEvent e) { text.selectAll(); } }); text.addKeyListener(new KeyAdapter() { boolean multi, typed; String old = ""; @Override public void keyPressed(final KeyEvent e) { final int page = getHeight() / getFont().getSize(); final int[] inds = list.getSelectedIndices(); final int op1 = inds.length == 0 ? -1 : inds[0]; final int op2 = inds.length == 0 ? -1 : inds[inds.length - 1]; int np1 = op1, np2 = op2; if(NEXTLINE.is(e)) { np2 = Math.min(op2 + 1, values.length - 1); } else if(PREVLINE.is(e)) { np1 = Math.max(op1 - 1, 0); } else if(NEXTPAGE.is(e)) { np2 = Math.min(op2 + page, values.length - 1); } else if(PREVPAGE.is(e)) { np1 = Math.max(op1 - page, 0); } else if(TEXTSTART.is(e)) { np1 = 0; } else if(TEXTEND.is(e)) { np2 = values.length - 1; } else { return; } final IntList il = new IntList(); for(int n = np1; n <= np2; n++) il.add(n); // choose new list value final int nv = op2 != np2 ? np2 : np1; final String val = values[nv]; list.setSelectedValue(val, true); if(e.isShiftDown() && !single) { list.setSelectedIndices(il.toArray()); text.setText(""); } else { list.setSelectedIndex(nv); text.setText(val); text.selectAll(); } multi = il.size() > 1; } @Override public void keyTyped(final KeyEvent e) { final char ch = e.getKeyChar(); if(num) { typed = ch >= '0' && ch <= '9'; if(!typed) e.consume(); } else { typed = ch >= ' ' && ch != 127; } multi = false; } @Override public void keyReleased(final KeyEvent e) { String txt = text.getText().trim().toLowerCase(); if(!txt.equals(old) && !multi) { final boolean glob = txt.matches("^.*[*?,].*$"); final String regex = glob ? IOFile.regex(txt, false) : null; final IntList il = new IntList(); for(int v = 0; v < values.length; ++v) { final String value = values[v].trim().toLowerCase(); if(glob) { if(value.matches(regex)) il.add(v); } else if(value.startsWith(txt)) { if(typed) { final int c = text.getCaretPosition(); text.setText(values[v]); text.select(c, values[v].length()); txt = value; } il.add(v); break; } } if(il.size() > 0) { list.setSelectedValue(values[il.get(il.size() - 1)], true); } list.setSelectedIndices(il.toArray()); } d.action(BaseXList.this); typed = false; old = txt; } }); add(text); final MouseInputAdapter mouse = new MouseInputAdapter() { @Override public void mouseEntered(final MouseEvent e) { BaseXLayout.focus(text); } @Override public void mousePressed(final MouseEvent e) { final Object[] i = list.getSelectedValues(); text.setText(i.length == 1 ? i[0].toString() : ""); text.requestFocusInWindow(); text.selectAll(); d.action(BaseXList.this); } @Override public void mouseDragged(final MouseEvent e) { mousePressed(e); } @Override public void mouseClicked(final MouseEvent e) { if(e.getClickCount() == 2) { d.close(); } } }; list = new JList(choice); list.setFocusable(false); if(s) list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.addMouseListener(mouse); list.addMouseMotionListener(mouse); text.setFont(list.getFont()); BaseXLayout.setWidth(text, list.getWidth()); BaseXLayout.addInteraction(list, d); scroll = new JScrollPane(list); add(scroll); setIndex(0); } /** * Chooses the specified value in the text field and list. * @param value the value to be set */ public void setValue(final String value) { list.setSelectedValue(value, true); text.setText(value); } @Override public void setEnabled(final boolean en) { list.setEnabled(en); text.setEnabled(en); } /** * Sets the specified font. * @param font font name * @param style style */ public void setFont(final String font, final int style) { final Font f = text.getFont(); text.setFont(new Font(font, style, f.getSize())); } /** * Returns all list choices. * @return list index entry */ public String[] getList() { return values; } /** * Returns the selected value of the text field. * An empty string is returned if no or multiple values are selected. * @return text field value */ public String getValue() { final Object[] vals = list.getSelectedValues(); return vals.length == 1 ? vals[0].toString() : ""; } /** * Returns all selected values. * @return text field value */ public StringList getValues() { final StringList sl = new StringList(); for(final Object o : list.getSelectedValues()) sl.add(o.toString()); return sl; } /** * Returns the numeric representation of the user input. If the * text field is invalid, the list entry is returned. * @return numeric value */ public int getNum() { final int i = Token.toInt(text.getText()); if(i != Integer.MIN_VALUE) return i; final Object value = list.getSelectedValue(); return value != null ? Token.toInt(value.toString()) : 0; } /** * Returns the current list index. * @return list index entry */ public int getIndex() { return list.getSelectedIndex(); } /** * Sets the specified list index. * @param i list entry */ public void setIndex(final int i) { if(i < values.length) setValue(values[i]); else text.setText(""); } @Override public void setSize(final int w, final int h) { BaseXLayout.setWidth(text, w); BaseXLayout.setSize(scroll, w, h); } /** * Sets the width of the component. * @param w width */ public void setWidth(final int w) { BaseXLayout.setWidth(text, w); BaseXLayout.setWidth(scroll, w); } /** * Resets the data shown in the list. * @param data list data */ public void setData(final String[] data) { values = data.clone(); list.setListData(data); setIndex(0); } @Override public boolean requestFocusInWindow() { return text.requestFocusInWindow(); } }