package org.basex.gui;
import static org.basex.gui.layout.BaseXKeys.*;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.plaf.basic.BasicComboPopup;
import org.basex.core.CommandParser;
import org.basex.data.Data;
import org.basex.gui.layout.BaseXCombo;
import org.basex.gui.layout.BaseXTextField;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.QuerySuggest;
import org.basex.util.list.StringList;
/**
* This class offers a text field for keyword and XQuery input.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
* @author Andreas Weiler
*/
public final class GUIInput extends BaseXTextField {
/** Reference to main window. */
final GUI gui;
/** JComboBox. */
final BaseXCombo box;
/** BasicComboPopup Menu. */
ComboPopup pop;
/** String for temporary input. */
private String pre = "";
/**
* Default constructor.
* @param main main window reference
*/
GUIInput(final GUI main) {
super(main);
gui = main;
final Font f = getFont();
setFont(f.deriveFont((float) f.getSize() + 2));
box = new BaseXCombo(main);
box.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
if(e.getModifiers() == InputEvent.BUTTON1_MASK) completeInput();
}
});
pop = new ComboPopup(box);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent e) {
final int count = box.getItemCount();
if(ENTER.is(e)) {
if(pop.isVisible()) {
completeInput();
} else {
// store current input in history
final String txt = getText();
final StringList sl = new StringList().add(txt);
final GUIProp gprop = gui.gprop;
final int i = main.context.data() == null ? 2 :
gprop.num(GUIProp.SEARCHMODE);
final String[] hs = i == 0 ? gprop.strings(GUIProp.SEARCH) :
i == 1 ? gprop.strings(GUIProp.XQUERY) :
gprop.strings(GUIProp.COMMANDS);
for(int p = 0; p < hs.length && sl.size() < 10; ++p) {
if(!hs[p].equals(txt)) sl.add(hs[p]);
}
gprop.set(i == 0 ? GUIProp.SEARCH : i == 1 ? GUIProp.XQUERY :
GUIProp.COMMANDS, sl.toArray());
// evaluate the input
if(e.getModifiers() == 0) main.execute();
}
return;
}
if(count == 0) return;
int bi = box.getSelectedIndex();
if(NEXTLINE.is(e)) {
if(!pop.isVisible()) {
showPopup();
} else {
if(++bi == count) bi = 0;
}
} else if(PREVLINE.is(e)) {
if(!pop.isVisible()) {
showPopup();
} else {
if(--bi < 0) bi = count - 1;
}
}
if(bi != box.getSelectedIndex()) box.setSelectedIndex(bi);
}
@Override
public void keyReleased(final KeyEvent e) {
if(ESCAPE.is(e)) {
pop.setVisible(false);
} else if(ENTER.is(e)) {
pop.hide();
} else if(!NEXTLINE.is(e) && !PREVLINE.is(e)) {
if(modifier(e) || control(e)) return;
showPopup();
// skip commands
if(gui.gprop.is(GUIProp.EXECRT) && !cmdMode()) main.execute();
}
}
});
}
@Override
public void setText(final String txt) {
super.setText(txt);
box.removeAllItems();
pop.setVisible(false);
}
/**
* Checks if the query is a command.
* @return result of check
*/
boolean cmdMode() {
return gui.gprop.num(GUIProp.SEARCHMODE) == 2 ||
gui.context.data() == null || getText().startsWith("!");
}
/**
* Completes the input with the current combobox choice.
*/
void completeInput() {
final Object sel = box.getSelectedItem();
if(sel == null) return;
final String suf = sel.toString();
final int pl = pre.length();
final int ll = pl > 0 ? pre.charAt(pl - 1) : ' ';
if(Character.isLetter(ll) && Character.isLetter(suf.charAt(0))) pre += " ";
setText(pre + sel);
showPopup();
if(gui.gprop.is(GUIProp.EXECRT) && !cmdMode()) gui.execute();
}
/**
* Shows the command popup menu.
*/
void showPopup() {
final String query = getText();
final int mode = gui.gprop.num(GUIProp.SEARCHMODE);
if(cmdMode()) {
cmdPopup(query);
} else if(mode == 1 || mode == 0 && query.startsWith("/")) {
queryPopup(query);
} else {
pop.setVisible(false);
}
}
/**
* Shows the command popup menu.
* @param query query input
*/
private void cmdPopup(final String query) {
StringList sl = null;
final boolean excl = query.startsWith("!");
try {
pre = excl ? "!" : "";
final String suf = getText().substring(pre.length());
new CommandParser(suf, gui.context).parse(true);
} catch(final QueryException ex) {
sl = ex.suggest();
final int marked = ex.markedCol() + (excl ? 2 : 1);
if(ex.markedCol() > -1 && marked <= query.length()) {
pre = query.substring(0, marked);
}
}
createCombo(sl);
}
/**
* Shows the xpath popup menu.
* @param query query input
*/
private void queryPopup(final String query) {
final Data data = gui.context.data();
if(data == null || !data.meta.pathindex) return;
StringList sl;
try {
final QuerySuggest qs = new QuerySuggest(query,
new QueryContext(gui.context), data);
qs.parse(null, null);
sl = qs.complete();
pre = query.substring(0, qs.qm);
} catch(final QueryException ex) {
sl = ex.suggest();
pre = query.substring(0, ex.col() - (ex.col() == 1 ? 1 : 0));
}
if(getCaretPosition() < pre.length()) sl = null;
createCombo(sl);
}
/**
* Creates and shows the combo box.
* @param sl strings to be added
*/
private void createCombo(final StringList sl) {
if(sl == null || sl.size() == 0) {
pop.setVisible(false);
return;
}
if(comboChanged(sl)) {
box.setModel(new DefaultComboBoxModel(sl.toArray()));
box.setSelectedIndex(-1);
pop = new ComboPopup(box);
}
final int w = getFontMetrics(getFont()).stringWidth(pre);
pop.show(this, Math.min(getWidth(), w), getHeight());
}
/**
* Tests if the combo box entries have changed.
* @param sl strings to be compared
* @return result of check
*/
private boolean comboChanged(final StringList sl) {
if(sl.size() != box.getItemCount()) return true;
final int is = sl.size();
for(int i = 0; i < is; ++i) {
if(!sl.get(i).equals(box.getItemAt(i))) return true;
}
return false;
}
/** Combo popup menu class, overriding the default constructor. */
private static final class ComboPopup extends BasicComboPopup {
/**
* Constructor.
* @param combo combobox reference
*/
ComboPopup(final JComboBox combo) {
super(combo);
final int h = combo.getMaximumRowCount();
setPreferredSize(new Dimension(getPreferredSize().width,
getPopupHeightForRowCount(h) + 2));
}
}
}