package org.geogebra.web.web.gui.view.spreadsheet;
import org.geogebra.common.gui.view.spreadsheet.RelativeCopy;
import org.geogebra.common.gui.view.spreadsheet.SpreadsheetController;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoElementSpreadsheet;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.plugin.EventType;
import org.geogebra.common.util.debug.Log;
import org.geogebra.web.html5.event.KeyEventsHandler;
import org.geogebra.web.html5.gui.inputfield.AutoCompleteTextFieldW;
import org.geogebra.web.html5.main.AppW;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.himamis.retex.editor.web.MathFieldW;
//import geogebra.web.gui.virtualkeyboard.VirtualKeyboard;
/**
* Default cell editor for the spreadsheet, extends
* DefaultCellEditor(JTextField)
*
*/
public class MyCellEditorW implements BaseCellEditor {
protected Kernel kernel;
protected AppW app;
protected GeoElementND value;
protected MyTableW table;
AutoCompleteTextFieldW autoCompleteTextField;
protected int column = -1;
protected int row = -1;
private boolean editing = false;
private boolean allowProcessGeo = false;
public boolean allowProcessGeo() {
return allowProcessGeo;
}
public void setAllowProcessGeo(boolean allowProcessGeo) {
this.allowProcessGeo = allowProcessGeo;
}
private boolean enableAutoComplete = false;
private SpreadsheetCellEditorKeyListener keyListener;
private boolean allowAutoEdit;
private SpreadsheetController controller;
public boolean isEnableAutoComplete() {
return enableAutoComplete;
}
public void setEnableAutoComplete(boolean enableAutoComplete) {
this.enableAutoComplete = enableAutoComplete;
autoCompleteTextField.setAutoComplete(enableAutoComplete);
}
public MyCellEditorW(Kernel kernel,
SimplePanel editorPanel, SpreadsheetController controller) {
this.controller = controller;
this.kernel = kernel;
app = (AppW) kernel.getApplication();
keyListener = new SpreadsheetCellEditorKeyListener(false);
autoCompleteTextField = new AutoCompleteTextFieldW(0,
(AppW) kernel.getApplication(), false, keyListener, false);
autoCompleteTextField.setAutoComplete(enableAutoComplete);
autoCompleteTextField.setStyleName("SpreadsheetEditorCell");
editorPanel.add(autoCompleteTextField);
}
public void setText(String text) {
if (!autoCompleteTextField.hasFocus() && !table.isDragging) {
autoCompleteTextField.setText(text);
}
}
public Widget getTableCellEditorWidget(MyTableW table0, Object value0,
boolean isSelected, int row0, int column0) {
table = table0;
if (value0 instanceof String) { // clicked to type
value = null;
} else {
value = (GeoElement) value0;
}
column = column0;
row = row0;
String text = "";
if (value != null) {
text = controller.getEditorInitString(value);
int index = text.indexOf("=");
if ((!value.isGeoText())) {
if (index == -1) {
text = "=" + text;
}
}
}
autoCompleteTextField.setText(text);
// autoCompleteTextField.setFont(app.getFontCanDisplay(text));
autoCompleteTextField.requestFocus();
editing = true;
return autoCompleteTextField;
}
/**
* set flag to require text start with "=" to activate autocomplete
*/
public void setEqualsRequired(boolean equalsRequired) {
autoCompleteTextField.setEqualsRequired(equalsRequired);
}
/**
* returns flag that requires text start with "=" to activate autocomplete
*/
public boolean isEqualsRequired() {
return autoCompleteTextField.isEqualsRequired();
}
public void setLabels() {
autoCompleteTextField.setDictionary(false);
}
public boolean textStartsWithEquals() {
String text = getEditingValue();
return text.startsWith("=");
}
/**
*
* @return true if the completion popup is open
*/
public boolean completionsPopupOpen() {
return autoCompleteTextField.getCompletions() != null;
}
// =======================================================
// In-cell Editing Methods
// =======================================================
public boolean isEditing() {
return editing;
}
public int getCaretPosition() {
return autoCompleteTextField.getCaretPosition();
}
/** Insert a geo label into current editor string. */
public void addLabel(String label) {
if (!editing) {
return;
}
// String text = (String) delegate.getCellEditorValue();
// delegate.setValue(text + label);
autoCompleteTextField.insertString(" " + label + " ");
}
public void setLabel(String text) {
if (!editing) {
return;
}
autoCompleteTextField.setText(text);
}
public String getEditingValue() {
return autoCompleteTextField.getText();
}
public Object getCellEditorValue() {
return value;
}
// =======================================================
// Stop/Cancel Editing
// =======================================================
@Override
public void cancelCellEditing() {
editing = false;
if (table != null) { // ?
table.finishEditing(false);
}
}
public boolean stopCellEditing() {
if (autoCompleteTextField.hasDummyCursor()) {
autoCompleteTextField.removeDummyCursor();
}
// try to redefine or create the cell geo with the current editing
// string
if (!processGeo()) {
return false;
}
editing = false;
boolean success = true;// TODO super.stopCellEditing();
return success;
}
boolean stopCellEditing(int colOff, int rowOff, boolean editNext) {
allowProcessGeo = true;
boolean success = stopCellEditing();
moveSelectedCell(colOff, rowOff);
allowProcessGeo = false;
table.finishEditing(editNext); // don't finish, we
if (editNext) {
table.setAllowEditing(true);
table.editCellAt(row + rowOff, column + colOff);
table.setAllowEditing(false);
// this should be deferred so that browser cannot steal focus from
// SS
autoCompleteTextField.getTextField().setFocus(true);
}
return success;
}
private void moveSelectedCell(int colOff, int rowOff) {
int nextRow = Math.min(row + rowOff, table.getRowCount());
int nextColumn = Math.min(column + colOff, table.getColumnCount());
table.setSelection(nextColumn, nextRow);
}
/**
* Attempts to create or redefine the cell geo using the current editing
* string
*
* @return
*/
private boolean processGeo() {
try {
if (allowProcessGeo) {
String text = autoCompleteTextField.getText();// ?// (String)
// delegate.getCellEditorValue();
// get GeoElement of current cell
value = kernel.lookupLabel(GeoElementSpreadsheet
.getSpreadsheetCellName(column, row));
if ("".equals(text)) {
if (value != null) {
value.removeOrSetUndefinedIfHasFixedDescendent();
value = null;
}
} else {
GeoElementND newVal = RelativeCopy
.prepareAddingValueToTableNoStoringUndoInfo(kernel,
app, text, value, column, row, false);
if (newVal == null) {
return false;
}
value = newVal;
}
if (value != null) {
app.storeUndoInfo();
}
}
} catch (Exception ex) {
// show GeoGebra error dialog
// kernel.getApplication().showError(ex.getMessage());
ex.printStackTrace();
// TODO super.stopCellEditing();
editing = false;
return false;
}
return true;
}
// =======================================================
// Key and Focus Listeners
// =======================================================
public void sendKeyPressEvent(KeyPressEvent e) {
autoCompleteTextField.getTextField().setFocus(true);
keyListener.onKeyPress(e);
}
public void sendKeyDownEvent(KeyDownEvent e) {
autoCompleteTextField.getTextField().setFocus(true);
keyListener.onKeyDown(e);
}
// keep track of when <tab> was first pressed
// so we can return to that column when <enter> pressed
private int tabReturnCol = -1;
public class SpreadsheetCellEditorKeyListener implements KeyEventsHandler {
// boolean escape = false;
boolean isFormulaBarListener;
public SpreadsheetCellEditorKeyListener(boolean isFormulaBarListener) {
this.isFormulaBarListener = isFormulaBarListener;
}
@Override
public void onKeyDown(KeyDownEvent e) {
// stopping propagation is needed to prevent duplicate events
e.stopPropagation();
checkCursorKeys(e);
int keyCode = e.getNativeKeyCode();
switch (keyCode) {
default:
// do nothing
break;
case KeyCodes.KEY_ESCAPE:
e.preventDefault();
GeoElement oldGeo = kernel.getGeoAt(column, row);
cancelCellEditing();
// restore old text in spreadsheet
table.getModel().setValueAt(oldGeo, row, column);
// stopCellEditing(0,0);
// force nice redraw
table.setSelection(column, row);
// update the formula bar after escape
// ?//table.getView().updateFormulaBar();
break;
}
}
@Override
public void onKeyPress(KeyPressEvent e) {
// iOS: we do receive the event but nothing is actually printed
// because focus moved from dummy textarea into editor
if (MathFieldW.checkCode(e.getNativeEvent(), "NumpadDecimal")) {
autoCompleteTextField.insertString(".");
e.preventDefault();
e.stopPropagation();
return;
}
final String charcode = e.getCharCode() + "";
if (MyCellEditorW.this.allowAutoEdit) {
app.invokeLater(new Runnable() {
@Override
public void run() {
String text = autoCompleteTextField.getText();
if (text == null || text.length() == 0) {
autoCompleteTextField.setText(charcode);
}
}
});
MyCellEditorW.this.allowAutoEdit = false;
}
// stopping propagation is needed to prevent
// the prevention of the default action at another place
e.stopPropagation();
}
@Override
public void onKeyUp(KeyUpEvent e) {
// stopping propagation may be needed in strange browsers
// this also makes sure no top-level action is done on keyUp
// but the default action of the event should have already been
// expired
e.stopPropagation();
}
public void checkCursorKeys(KeyDownEvent e) {
String text = autoCompleteTextField.getText();// ?// (String)
// delegate.getCellEditorValue();
int keyCode = e.getNativeKeyCode();
// Application.debug(e+"");
switch (keyCode) {
default:
// do nothing
break;
case KeyCodes.KEY_UP:
if(isSuggesting()){
return;
}
if (isFormulaBarListener) {
return;
}
// Application.debug("UP");
stopCellEditing(0, -1, false);
// ?//e.consume();
setTabReturnCol(-1);
break;
case KeyCodes.KEY_TAB:
if (isFormulaBarListener) {
return;
}
Log.debug(" tab");
// Application.debug("RIGHT");
// shift-tab moves left
// tab moves right
if (getTabReturnCol() == -1) {
setTabReturnCol(column);
}
stopCellEditing(e.isShiftKeyDown() ? -1 : 1, 0, false);
e.preventDefault();
break;
case KeyCodes.KEY_ENTER:
if(isSuggesting()){
return;
}
// if incomplete command entered, want to move the cursor to
// between []
int bracketsIndex = text.indexOf("[]");
if (bracketsIndex == -1) {
if (getTabReturnCol() != -1) {
int colOffset = getTabReturnCol() - column;
stopCellEditing(colOffset, 1, true);
} else {
// TODO: in desktop this works with column, row + 1
String cellBelowStr = GeoElementSpreadsheet
.getSpreadsheetCellName(column, row + 1);
GeoElement cellBelow = kernel.getConstruction()
.lookupLabel(cellBelowStr);
boolean moveDown = cellBelow == null
|| !cellBelow.isProtected(EventType.UPDATE);
// don't move down to cell below after <Enter> if it's
// fixed
stopCellEditing(0, moveDown ? 1 : 0, moveDown);
}
} else {
autoCompleteTextField.setCaretPosition(bracketsIndex + 1);
// ?//e.consume();
}
setTabReturnCol(-1);
break;
case KeyCodes.KEY_DOWN:
if(isSuggesting()){
return;
}
if (isFormulaBarListener) {
// ?//e.consume();
return;
}
// Application.debug("DOWN");
stopCellEditing(0, 1, false);
setTabReturnCol(-1);
break;
case KeyCodes.KEY_LEFT:
if (isFormulaBarListener) {
return;
}
// Application.debug("LEFT");
// Allow left/right keys to exit cell for easier data entry
if (getCaretPosition() == 0) {
stopCellEditing(-1, 0, false);
}
setTabReturnCol(-1);
break;
case KeyCodes.KEY_RIGHT:
if (isFormulaBarListener) {
return;
}
// Application.debug("RIGHT");
// Allow left/right keys to exit cell for easier data entry
if (getCaretPosition() == text.length()) {
stopCellEditing(1, 0, false);
}
setTabReturnCol(-1);
break;
case KeyCodes.KEY_PAGEDOWN:
case KeyCodes.KEY_PAGEUP:
e.preventDefault();
// ?//e.consume();
setTabReturnCol(-1);
break;
// An F1 keypress causes the focus to be lost, so we
// need to set 'editing' to false to prevent the focusLost()
// method from calling stopCellEditing()
// ?//case KeyEvent.VK_F1:
// ?// editing = false;
// ?// break;
}
}
}
public Widget getTextfield() {
return autoCompleteTextField;
}
boolean isSuggesting(){
return autoCompleteTextField.isSuggesting();
}
public void allowAutoEdit() {
this.allowAutoEdit = true;
}
protected int getTabReturnCol() {
return tabReturnCol;
}
protected void setTabReturnCol(int tabReturnCol) {
this.tabReturnCol = tabReturnCol;
}
public void onEnter() {
if (tabReturnCol > -1) {
table.changeSelection(row, tabReturnCol, false);
setTabReturnCol(-1);
}
}
/**
* Selects all the text in editor.
*/
public void selectAll() {
autoCompleteTextField.getTextField().setFocus(true);
autoCompleteTextField.selectAll();
}
}