text: package org.geogebra.desktop.gui.view.spreadsheet;
  
  import java.awt.Component;
  import java.awt.event.FocusEvent;
  import java.awt.event.FocusListener;
  import java.awt.event.KeyEvent;
  import java.awt.event.KeyListener;
  
  import javax.swing.DefaultCellEditor;
  import javax.swing.JTable;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  
  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.desktop.gui.inputfield.AutoCompleteTextFieldD;
  import org.geogebra.desktop.gui.inputfield.KeyNavigation;
  import org.geogebra.desktop.gui.virtualkeyboard.VirtualKeyboardD;
  import org.geogebra.desktop.main.AppD;
  
  /**
   * Default cell editor for the spreadsheet, extends
   * DefaultCellEditor(JTextField)
   * 
   */
  public class MyCellEditorSpreadsheet extends DefaultCellEditor
  		implements FocusListener {
  
  	private static final long serialVersionUID = 1L;
  
  	protected Kernel kernel;
  	protected AppD app;
  	protected GeoElementND value;
  	protected MyTableD table;
  	AutoCompleteTextFieldD textField;
  
  	protected int column;
  	protected int row;
  	boolean editing = false;
  	private boolean errorOnStopEditing = false;
  
  	private boolean allowProcessGeo = false;
  
  	public boolean allowProcessGeo() {
  		return allowProcessGeo;
  	}
  
  	public void setAllowProcessGeo(boolean allowProcessGeo) {
  		this.allowProcessGeo = allowProcessGeo;
  	}
  
  	private boolean enableAutoComplete = false;
  
  	private SpreadsheetController controller;
  
  	public boolean isEnableAutoComplete() {
  		return enableAutoComplete;
  	}
  
  	public void setEnableAutoComplete(boolean enableAutoComplete) {
  		this.enableAutoComplete = enableAutoComplete;
  		textField.setAutoComplete(enableAutoComplete);
  	}
  
  	public MyCellEditorSpreadsheet(Kernel kernel,
  			SpreadsheetController controller) {
  
  		super(new AutoCompleteTextFieldD(0, (AppD) kernel.getApplication(),
  				KeyNavigation.IGNORE));
  		this.kernel = kernel;
  		this.controller = controller;
  		app = (AppD) kernel.getApplication();
  		textField = (AutoCompleteTextFieldD) editorComponent;
  		textField.setAutoComplete(enableAutoComplete);
  
  		editorComponent
  				.addKeyListener(new SpreadsheetCellEditorKeyListener(false));
  		editorComponent.addFocusListener(this);
  
  		DocumentListener documentListener = new DocumentListener() {
  			@Override
  			public void changedUpdate(DocumentEvent documentEvent) {
  				// do nothing
  			}
  
  			@Override
  			public void insertUpdate(DocumentEvent documentEvent) {
  				updateFormulaBar(documentEvent);
  			}
  
  			@Override
  			public void removeUpdate(DocumentEvent documentEvent) {
  				updateFormulaBar(documentEvent);
  			}
  
  			private void updateFormulaBar(DocumentEvent documentEvent) {
  				if (table.view.getShowFormulaBar()
  						&& (textField.hasFocus() || table.isDragging2)) {
  					table.view.getFormulaBar()
  							.setEditorText(textField.getText());
  				}
  			}
  		};
  		textField.getDocument().addDocumentListener(documentListener);
  
  	}
  
  	public void setText(String text) {
  		if (!textField.hasFocus() && !table.isDragging2) {
  			textField.setText(text);
  		}
  
  	}
  
  	@Override
  	public Component getTableCellEditorComponent(JTable table0, Object value0,
  			boolean isSelected, int row0, int column0) {
  
  		if (table0 instanceof MyTableD) {
  			table = (MyTableD) table0;
  		} else {
  			return null;
  		}
  
  		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;
  				}
  			}
  		}
  		delegate.setValue(text);
  
  		Component component = getComponent();
  		component.setFont(app.getFontCanDisplayAwt(text));
  
  		editing = true;
  
  		return component;
  	}
  
  	/**
  	 * set flag to require text start with "=" to activate autocomplete
  	 */
  	public void setEqualsRequired(boolean equalsRequired) {
  		textField.setEqualsRequired(equalsRequired);
  	}
  
  	/**
  	 * returns flag that requires text start with "=" to activate autocomplete
  	 */
  	public boolean isEqualsRequired() {
  		return textField.isEqualsRequired();
  	}
  
  	public void setLabels() {
  		textField.setDictionary(true);
  	}
  
  	/**
  	 * 
  	 * @return true if the completion popup is open
  	 */
  	public boolean completionsPopupOpen() {
  		return textField.getCompletions() != null;
  	}
  
  	// =======================================================
  	// In-cell Editing Methods
  	// =======================================================
  
  	public boolean isEditing() {
  		return editing;
  	}
  
  	public int getCaretPosition() {
  		return textField.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);
  		textField.replaceSelection(" " + label + " ");
  	}
  
  	public void setLabel(String text) {
  		if (!editing) {
  			return;
  		}
  		delegate.setValue(text);
  	}
  
  	public String getEditingValue() {
  		return (String) delegate.getCellEditorValue();
  	}
  
  	@Override
  	public Object getCellEditorValue() {
  		return value;
  	}
  
  	// =======================================================
  	// Stop/Cancel Editing
  	// =======================================================
  
  	@Override
  	public void cancelCellEditing() {
  		editing = false;
  		errorOnStopEditing = false;
  
  		super.cancelCellEditing();
  
  		// give the table the focus in case the formula bar is the editor
  		if (table.getView().getFormulaBar().editorHasFocus()) {
  			// Application.debug("give focus to table");
  			table.requestFocus();
  		}
  	}
  
  	@Override
  	public boolean stopCellEditing() {
  
  		errorOnStopEditing = true; // flag to handle column resizing during
  									// editing (see focusLost method)
  
  		// try to redefine or create the cell geo with the current editing
  		// string
  		if (!processGeo()) {
  			return false;
  		}
  
  		errorOnStopEditing = false;
  		editing = false;
  		boolean success = super.stopCellEditing();
  
  		// give the table the focus in case the formula bar is the editor
  		if (table.getView().getFormulaBar().editorHasFocus()) {
  			// Application.debug("give focus to table");
  			table.requestFocus();
  		}
  		return success;
  	}
  
  	boolean stopCellEditing(int colOff, int rowOff) {
  		allowProcessGeo = true;
  		boolean success = stopCellEditing();
  		moveSelectedCell(colOff, rowOff);
  		allowProcessGeo = false;
  		return success;
  	}
  
  	private void moveSelectedCell(int colOff, int rowOff) {
  		int nextRow = Math.min(row + rowOff, table.getRowCount() - 1);
  		int nextColumn = Math.min(column + colOff, table.getColumnCount() - 1);
  		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 = (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();
  			super.stopCellEditing();
  			editing = false;
  			return false;
  		}
  		return true;
  	}
  
  	// =======================================================
  	// Key and Focus Listeners
  	// =======================================================
  
  	/**
  	 * keep track of when <tab> was first pressed so we can return to that
  	 * column when <enter> pressed
  	 */
  	public int tabReturnCol = -1;
  
  	public class SpreadsheetCellEditorKeyListener implements KeyListener {
  
  		// boolean escape = false;
  		boolean isFormulaBarListener;
  
  		public SpreadsheetCellEditorKeyListener(boolean isFormulaBarListener) {
  			this.isFormulaBarListener = isFormulaBarListener;
  		}
  
  		@Override
  		public void keyTyped(KeyEvent e) {
  			//
  		}
  
  		@Override
  		public void keyPressed(KeyEvent e) {
  			checkCursorKeys(e);
  			int keyCode = e.getKeyCode();
  
  			if (keyCode == KeyEvent.VK_ESCAPE) {
  				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();
  
  			}
  		}
  
  		@Override
  		public void keyReleased(KeyEvent e) {
  			//
  		}
  
  		public void checkCursorKeys(KeyEvent e) {
  
  			String text = (String) delegate.getCellEditorValue();
  
  			int keyCode = e.getKeyCode();
  			// Application.debug(e+"");
  			switch (keyCode) {
  			default:
  				// do nothing
  				break;
  			case KeyEvent.VK_UP:
  				if (isFormulaBarListener) {
  					return;
  				}
  
  				// Application.debug("UP");
  				stopCellEditing(0, -1);
  				editing = false;
  				e.consume();
  				tabReturnCol = -1;
  				break;
  
  			case KeyEvent.VK_TAB:
  				if (isFormulaBarListener) {
  					return;
  				}
  				Log.debug(" tab");
  				// Application.debug("RIGHT");
  				// shift-tab moves left
  				// tab moves right
  				if (tabReturnCol == -1) {
  					tabReturnCol = column;
  				}
  				stopCellEditing(e.isShiftDown() ? -1 : 1, 0);
  				editing = false;
  
  				break;
  
  			case KeyEvent.VK_ENTER:
  				// if incomplete command entered, want to move the cursor to
  				// between []
  				int bracketsIndex = text.indexOf("[]");
  				if (bracketsIndex == -1) {
  
  					if (tabReturnCol != -1) {
  						int colOffset = tabReturnCol - column;
  						stopCellEditing(colOffset, 1);
  						editing = false;
  					} else {
  
  						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);
  
  					}
  				} else {
  					textField.setCaretPosition(bracketsIndex + 1);
  					e.consume();
  				}
  
  				tabReturnCol = -1;
  				break;
  
  			case KeyEvent.VK_DOWN:
  				if (isFormulaBarListener) {
  					e.consume();
  					return;
  				}
  				// Application.debug("DOWN");
  				stopCellEditing(0, 1);
  				editing = false;
  				tabReturnCol = -1;
  				break;
  
  			case KeyEvent.VK_LEFT:
  				if (isFormulaBarListener) {
  					return;
  				}
  				// Application.debug("LEFT");
  				// Allow left/right keys to exit cell for easier data entry
  				if (getCaretPosition() == 0) {
  					stopCellEditing(-1, 0);
  					editing = false;
  				}
  				editing = false;
  				tabReturnCol = -1;
  				break;
  
  			case KeyEvent.VK_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);
  					editing = false;
  				}
  
  				editing = false;
  				tabReturnCol = -1;
  				break;
  
  			case KeyEvent.VK_PAGE_DOWN:
  			case KeyEvent.VK_PAGE_UP:
  				e.consume();
  				tabReturnCol = -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;
  
  			}
  
  		}
  
  	}
  
  	@Override
  	public void focusGained(FocusEvent arg0) {
  		editing = true;
  	}
  
  	@Override
  	public void focusLost(FocusEvent arg0) {
  
  		// VirtualKeyboard gets the focus very briefly when opened
  		// so ignore this!
  		if (arg0.getOppositeComponent() instanceof VirtualKeyboardD) {
  			return;
  		}
  
  		// only needed if eg columns resized
  		if (editing) {
  			if (!errorOnStopEditing) {
  
  				// Process the current edit, exit the editor and update the
  				// formula bar
  				setAllowProcessGeo(true);
  				stopCellEditing();
  				setAllowProcessGeo(false);
  				table.getView().getFormulaBar().update();
  
  			} else if (!app.isErrorDialogShowing()) {
  				cancelCellEditing();
  			}
  		}
  	}
  
  }
  