package org.geogebra.web.web.cas.view;
import java.util.Collections;
import java.util.TreeSet;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.cas.view.CASTable;
import org.geogebra.common.cas.view.CASTableCellEditor;
import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant;
import org.geogebra.common.kernel.geos.GeoCasCell;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.main.App;
import org.geogebra.common.util.debug.Log;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.web.util.ReTeXHelper;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.event.dom.client.HumanInputEvent;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable;
import com.google.gwt.user.client.ui.Widget;
public class CASTableW extends Grid implements CASTable {
public static final int COL_CAS_CELLS_WEB = 1;
public static final int COL_CAS_HEADER = 0;
private CASTableCellEditor editor;
private CASTableCellW editing;
private AppW app;
private int[] selectedRows = new int[0];
private CASTableControllerW ml;
private CASViewW view;
public CASTableW(AppW app, CASTableControllerW ml, CASViewW casViewW) {
super(0, 2);
this.app = app;
this.ml = ml;
addStyleName("CAS-table");
insertRow(0, null, false);
view = casViewW;
}
@Override
public void setLabels() {
if (hasEditor()) {
getEditor().setLabels();
}
}
@Override
public GeoCasCell getGeoCasCell(int n) {
if (n >= 0 && this.getRowCount() > n) {
Widget w = getWidget(n, COL_CAS_CELLS_WEB);
if (w instanceof CASTableCellW) {
return ((CASTableCellW) w).getCASCell();
}
}
return null;
}
@Override
public App getApplication() {
return app;
}
@Override
public void deleteAllRows() {
resize(0, 2);
}
@Override
public void insertRow(int rows, GeoCasCell casCell, boolean b) {
int n = rows;
if (n >= getRowCount()) {
resize(n + 1, 2);
} else {
this.insertRow(n);
}
// update keys (rows) in arbitrary constant table
updateAfterInsertArbConstTable(rows);
CASTableCellW cellWidget = new CASTableCellW(casCell, app);
Widget rowHeader = new RowHeaderWidget(this, n + 1, casCell,
(AppW) getApplication());
Widget outputWidget = cellWidget.getOutputWidget();
outputWidget.addDomHandler(ml, MouseUpEvent.getType());
outputWidget.addDomHandler(ml, TouchEndEvent.getType());
setWidget(n, CASTableW.COL_CAS_HEADER, rowHeader);
getCellFormatter().addStyleName(n, COL_CAS_HEADER, "cas_header");
setWidget(n, CASTableW.COL_CAS_CELLS_WEB, cellWidget);
if (n < getRowCount() - 1) {
// Let increase the labels below the n. row.
resetRowNumbers(n + 1);
// tell construction about new GeoCasCell if it is not at the
// end
app.getKernel().getConstruction().setCasCellRow(casCell, rows);
}
}
/**
* Updates arbitraryConstantTable in construction.
*
* @param row
* row index (starting from 0) where cell insertion is done
*/
private void updateAfterInsertArbConstTable(int row) {
if (app.getKernel().getConstruction().getArbitraryConsTable()
.size() > 0) {
// find last row number
Integer max = Collections.max(app.getKernel().getConstruction()
.getArbitraryConsTable().keySet());
for (int key = max; key >= row; key--) {
MyArbitraryConstant myArbConst = app.getKernel()
.getConstruction()
.getArbitraryConsTable().get(key);
if (myArbConst != null
&& !app.getKernel().getConstruction().isCasCellUpdate()
&& !app.getKernel().getConstruction().isFileLoading()
&& app.getKernel().getConstruction().isNotXmlLoading()) {
app.getKernel().getConstruction().getArbitraryConsTable()
.remove(key);
app.getKernel().getConstruction().getArbitraryConsTable()
.put(key + 1, myArbConst);
}
}
}
}
@Override
public void resetRowNumbers(int from) {
RowHeaderWidget nextHeader;
for (int i = from; i < getRowCount(); i++) {
nextHeader = (RowHeaderWidget) this.getWidget(i, COL_CAS_HEADER);
nextHeader.setLabel(i + 1 + "");
}
}
@Override
public int[] getSelectedRows() {
return selectedRows;
}
@Override
public int getSelectedRow() {
if (selectedRows.length < 1) {
return -1;
}
return selectedRows[0];
}
@Override
public void stopEditing() {
if (editing != null) {
editing.stopEditing();
}
editing = null;
}
/**
* Stop editing without comitting changes
*/
public void cancelEditing() {
if (editing != null) {
editing.cancelEditing();
}
editing = null;
}
@Override
public void startEditingRow(int n) {
startEditingRow(n, null);
}
private void startEditingRow(int n, String newText) {
if (n == 0) {
setFirstRowFront(true);
}
Widget w = getWidget(n, COL_CAS_CELLS_WEB);
if (app != null) {
app.getGlobalKeyDispatcher().setFocused(true);
}
if (w == editing && newText == null) {
getEditor().ensureEditing();
return;
}
setSelectedRows(n, n);
// cancelEditing();
stopEditing();
Log.debug(n + ":" + (w == null ? "null" : w.getClass()));
if (w instanceof CASTableCellW) {
// App.debug("cell found");
editing = (CASTableCellW) w;
((CASEditorW) getEditor()).resetInput();
((CASEditorW) getEditor())
.setAutocomplete(editing.getCASCell() == null
|| !editing.getCASCell().isUseAsText());
editing.startEditing(((CASEditorW) getEditor()),
newText);
}
}
@Override
public CASTableCellEditor getEditor() {
if (editor == null) {
editor = new ReTeXHelper()
.getCASEditor(this, app, ml);
}
return editor;
}
@Override
public void deleteRow(int rowNumber) {
removeRow(rowNumber);
resetRowNumbers(rowNumber);
// update keys (rows) in arbitrary constant table
updateAfterDeleteArbConstTable(rowNumber);
}
/**
* Updates arbitraryConstantTable in construction.
*
* @param row
* row index (starting from 0) where cell is deleted
*/
private void updateAfterDeleteArbConstTable(int row) {
MyArbitraryConstant arbConst = app.getKernel().getConstruction()
.getArbitraryConsTable().remove(row);
if (arbConst != null) {
for (GeoNumeric geoNum : arbConst.getConstList()) {
app.getKernel().getConstruction()
.removeFromConstructionList(geoNum);
app.getKernel().getConstruction().removeLabel(geoNum);
app.getKernel().notifyRemove(geoNum);
}
}
if (app.getKernel().getConstruction().getArbitraryConsTable()
.size() > 0) {
// find last row number
Integer max = Collections.max(app.getKernel().getConstruction()
.getArbitraryConsTable().keySet());
for (int key = row + 1; key <= max; key++) {
MyArbitraryConstant myArbConst = app.getKernel()
.getConstruction()
.getArbitraryConsTable().get(key);
if (myArbConst != null) {
app.getKernel().getConstruction().getArbitraryConsTable()
.remove(key);
app.getKernel().getConstruction().getArbitraryConsTable()
.put(key - 1,
myArbConst);
}
}
}
}
@Override
public void setRow(int rowNumber, GeoCasCell casCell) {
if (rowNumber < 0) {
return;
}
if (rowNumber >= this.getRowCount()) {
resize(rowNumber + 1, 2);
}
if (casCell.isUseAsText() && editing != null) {
editing.setInput();
}
CASTableCellW cellWidget = new CASTableCellW(casCell, app);
Widget rowHeader = new RowHeaderWidget(this, rowNumber + 1, casCell,
(AppW) getApplication());
Widget outputWidget = cellWidget.getOutputWidget();
outputWidget.addDomHandler(ml, MouseUpEvent.getType());
outputWidget.addDomHandler(ml, TouchEndEvent.getType());
setWidget(rowNumber, CASTableW.COL_CAS_HEADER, rowHeader);
setWidget(rowNumber, CASTableW.COL_CAS_CELLS_WEB, cellWidget);
if (casCell.isUseAsText()) {
cellWidget.setFont();
cellWidget.setColor();
}
}
private void setRowSelected(int rowNumber, boolean selected) {
if (selected) {
getCellFormatter().getElement(rowNumber, COL_CAS_HEADER)
.addClassName("selected");
} else {
getCellFormatter().getElement(rowNumber, COL_CAS_HEADER)
.removeClassName("selected");
}
}
@Override
public boolean isEditing() {
return editing != null;
}
/**
* Convert event into cell coordinates
*
* @param event
* mouise / touch event
* @return (column, row)
*/
public GPoint getPointForEvent(HumanInputEvent<?> event) {
Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
if (td == null) {
return null;
}
int row = TableRowElement.as(td.getParentElement())
.getSectionRowIndex();
int column = TableCellElement.as(td).getCellIndex();
return new GPoint(column, row);
}
/**
* @param from
* min selected row
* @param to
* max selected row
*/
public void setSelectedRows(int from, int to) {
selectedRows = new int[0];
addSelectedRows(from, to);
view.getCASStyleBar().setSelectedRow(getGeoCasCell(from));
}
/**
* @param a
* min or max selected row
* @param b
* min or max selected row
*/
public void addSelectedRows(int a, int b) {
int from = Math.min(a, b);
int to = Math.max(a, b);
if (from < 0) {
return;
}
for (int i = 0; i < getRowCount(); i++) {
markRowSelected(i, false);
}
TreeSet<Integer> newSelectedRows = new TreeSet<Integer>();
// add old rows
for (int i = 0; i < selectedRows.length; i++) {
newSelectedRows.add(selectedRows[i]);
markRowSelected(selectedRows[i], true);
}
// add new rows
for (int i = from; i <= to; i++) {
newSelectedRows.add(i);
markRowSelected(i, true);
}
int j = 0;
selectedRows = new int[newSelectedRows.size()];
for (int row : newSelectedRows) {
selectedRows[j++] = row;
}
}
private void markRowSelected(int rowNumber, boolean b) {
setRowSelected(rowNumber, b);
}
/**
* @return CAS view
*/
public CASViewW getCASView() {
return view;
}
public boolean isSelectedIndex(int row) {
for (Integer item : getSelectedRows()) {
if (item.equals(row)) {
return true;
}
}
return false;
}
public int getEditingRow() {
if (isEditing()) {
return getSelectedRows()[0];
}
return -1;
}
public CASTableCellW getEditingCell() {
return editing;
}
public void setFirstRowFront(boolean value) {
CellFormatter cellFormatter = getCellFormatter();
if (value) {
cellFormatter.addStyleName(0, COL_CAS_CELLS_WEB,
"CAS_table_first_row_selected");
} else {
cellFormatter.removeStyleName(0, COL_CAS_CELLS_WEB,
"CAS_table_first_row_selected");
}
}
public CASTableCellW getCasCellForEvent(HumanInputEvent<?> event) {
Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
if (td == null) {
return null;
}
int row = TableRowElement.as(td.getParentElement())
.getSectionRowIndex();
int column = TableCellElement.as(td).getCellIndex();
Widget widget = getWidget(row, column);
if (!(widget instanceof CASTableCellW)) {
return null;
}
return (CASTableCellW) widget;
}
/**
* Return value for {@link HTMLTable#getCellForEvent}.
*/
public class MyCell extends HTMLTable.Cell {
public MyCell(int rowIndex, int cellIndex) {
super(rowIndex, cellIndex);
}
}
/**
* Given a click event, return the Cell that was clicked or touched, or null
* if the event did not hit this table. The cell can also be null if the
* click event does not occur on a specific cell.
*
* @param event
* A click event of indeterminate origin
* @return The appropriate cell, or null
*/
public MyCell getCellForEvent(HumanInputEvent<?> event) {
Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
if (td == null) {
return null;
}
int row = TableRowElement.as(td.getParentElement())
.getSectionRowIndex();
int column = TableCellElement.as(td).getCellIndex();
return new MyCell(row, column);
}
public void setCellInput(int i, String cellInput) {
GeoCasCell casCell = getGeoCasCell(i);
if (casCell != null && cellInput != null && cellInput.length() > 0) {
// casCell.setInput(toBeCopied);
// casCell.setLaTeXInput(null);
startEditingRow(i, cellInput);
}
}
@Override
public boolean hasEditor() {
return this.editor != null;
}
public boolean keepEditing(boolean failure, int rowNum) {
if (failure) {
Widget widget = getWidget(rowNum, COL_CAS_CELLS_WEB);
if (widget instanceof CASTableCellW) {
((CASTableCellW) widget).showError();
}
}
return false;
}
}