package org.basex.gui;
import static org.basex.gui.layout.BaseXKeys.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
import org.basex.core.*;
import org.basex.core.parse.*;
import org.basex.data.*;
import org.basex.gui.layout.*;
import org.basex.query.*;
import org.basex.util.list.*;
import org.basex.util.options.*;
/**
* This class offers a text field for keyword and XQuery input.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
public final class GUIInput extends BaseXTextField {
/** Reference to main window. */
private final GUI gui;
/** JComboBox. */
private final BaseXCombo box;
/** BasicComboPopup Menu. */
private GUIInputPopup 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();
final int fs = f.getSize(), ns = (int) (fs * 1.2d);
setFont(f.deriveFont(ns));
final Dimension ps = getPreferredSize();
setPreferredSize(new Dimension(ps.width, ps.height + ns - fs));
box = new BaseXCombo(main);
box.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
if(e.getModifiers() == InputEvent.BUTTON1_MASK) completeInput();
}
});
pop = new GUIInputPopup(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();
}
// evaluate the input
if(e.getModifiers() == 0) gui.execute();
}
if(count == 0) return;
int bi = box.getSelectedIndex();
if(NEXTLINE.is(e)) {
if(pop.isVisible()) {
if(++bi == count) bi = 0;
} else {
showPopup();
}
} else if(PREVLINE.is(e)) {
if(pop.isVisible()) {
if(--bi < 0) bi = count - 1;
} else {
showPopup();
}
}
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.gopts.get(GUIOptions.EXECRT) && !cmdMode()) main.execute();
}
}
});
}
@Override
public void setText(final String txt) {
super.setText(txt);
box.removeAllItems();
pop.setVisible(false);
}
@Override
public void store() {
// store current input in history
final Data data = gui.context.data();
final int i = data == null ? 2 : gui.gopts.get(GUIOptions.SEARCHMODE);
final StringsOption options = i == 0 ? GUIOptions.SEARCH : i == 1 ?
GUIOptions.XQUERY : GUIOptions.COMMANDS;
new BaseXHistory(gui, options).store(getText());
}
/**
* Sets the input mode.
* @param mode mode
*/
void mode(final String mode) {
hint(mode + Text.DOTS).setText("");
}
/**
* Checks if the query is a command.
* @return result of check
*/
private boolean cmdMode() {
return gui.gopts.get(GUIOptions.SEARCHMODE) == 2 ||
gui.context.data() == null || getText().startsWith("!");
}
/**
* Completes the input with the current combobox choice.
*/
private void completeInput() {
final String suf = box.getSelectedItem();
if(suf == null) return;
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 + suf);
showPopup();
if(gui.gopts.get(GUIOptions.EXECRT) && !cmdMode()) gui.execute();
}
/**
* Shows the command popup menu.
*/
private void showPopup() {
final String query = getText();
final int mode = gui.gopts.get(GUIOptions.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());
CommandParser.get(suf, gui.context).suggest().parse();
} catch(final QueryException ex) {
sl = ex.suggest();
final int marked = ex.markedColumn() + (excl ? 2 : 1);
if(ex.markedColumn() > -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) return;
StringList sl;
try(QueryContext qc = new QueryContext(gui.context)) {
final QuerySuggest qs = new QuerySuggest(query, qc, data);
qs.parseMain();
sl = qs.complete();
pre = query.substring(0, qs.mark);
} catch(final QueryException ex) {
sl = ex.suggest();
pre = query.substring(0, ex.column() - 1);
}
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.isEmpty()) {
pop.setVisible(false);
return;
}
if(comboChanged(sl)) {
box.setModel(new DefaultComboBoxModel<>(sl.toArray()));
box.setSelectedIndex(-1);
pop = new GUIInputPopup(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 GUIInputPopup extends BasicComboPopup {
/**
* Constructor.
* @param combo combobox reference
*/
GUIInputPopup(final JComboBox<String> combo) {
super(combo);
final int h = combo.getMaximumRowCount();
setPreferredSize(new Dimension(getPreferredSize().width, getPopupHeightForRowCount(h) + 2));
}
}
}