package org.geogebra.common.cas.view; import org.geogebra.common.cas.GeoGebraCAS; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.gui.Editing; import org.geogebra.common.gui.SetLabels; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.ModeSetter; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.geos.GProperty; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.main.App; import org.geogebra.common.util.debug.Log; /** * Platform independent abstract CAS view */ public abstract class CASView implements Editing, SetLabels { /** Default CAS toolbar */ public static final String TOOLBAR_DEFINITION_D = "1001 | 1002 | 1003 || 1005 | 1004 || 1006 | 1007 | 1010 || 1008 1009 || 66 68 || 6"; /** * Default CAS toolbar for Web (before the prob. calc and function inspector * are implemented) */ public static final String TOOLBAR_DEFINITION = "1001 | 1002 | 1003 || 1005 | 1004 || 1006 | 1007 | 1010 || 1008 | 1009 || 6"; private GeoGebraCAS cas; /** kernel */ final protected Kernel kernel; /** input handler */ private CASInputHandler casInputHandler; /** * @param kernel2 * kernel */ public CASView(Kernel kernel2) { kernel = kernel2; getCAS(); } /** * @return CAS table */ public abstract CASTable getConsoleTable(); /** * @return application */ public abstract App getApp(); /** * Shows dialog for substitution tool * * @param prefix * prefix (keep as is) * @param evalText * evaluable text (do substitution here) * @param postfix * postfix (keep as is again) * @param selRow * row index (starting from 0) */ public abstract void showSubstituteDialog(String prefix, String evalText, String postfix, int selRow); /** * Updates labels to match current locale */ @Override public void setLabels() { getConsoleTable().setLabels(); } @Override public int getViewID() { return App.VIEW_CAS; } /** * Returns the output string in the n-th row of this CAS view. * * @param n * row index (starting from 0) * @return output value */ public String getRowOutputValue(int n) { ValidExpression outVE = getConsoleTable().getGeoCasCell(n) .getOutputValidExpression(); // if we don't have an outputVE, we let GeoCasCell deal with it :) if (outVE == null) { return getConsoleTable().getGeoCasCell(n) .getOutput(StringTemplate.numericDefault); } if (outVE.unwrap() instanceof GeoElement) { return ((GeoElement) outVE.unwrap()) .toOutputValueString(StringTemplate.numericDefault); } return outVE.toString(StringTemplate.numericDefault); } /** * Returns the input string in the n-th row of this CAS view. If the n-th * cell has no output string, the input string of this cell is returned. * * @param n * row index (starting from 0) * @return input string in the n-th row of this CAS view */ public String getRowInputValue(int n) { return getConsoleTable().getGeoCasCell(n) .getInput(StringTemplate.defaultTemplate); } /** * Returns the number of rows of this CAS view. * * @return the number of rows of this CAS view. */ public int getRowCount() { return getConsoleTable().getRowCount(); } /** * @return the GoGebraCAS used by this view */ final public synchronized GeoGebraCAS getCAS() { if (cas == null) { cas = (GeoGebraCAS) kernel.getGeoGebraCAS(); } return cas; } /** * Handles toolbar mode changes */ @Override public void setMode(int mode, ModeSetter m) { if (m != ModeSetter.TOOLBAR && m != ModeSetter.CAS_BLUR) { return; } boolean focus = m == ModeSetter.TOOLBAR; String command = EuclidianConstants.getModeTextSimple(mode); // e.g. // "Derivative" boolean backToEvaluate = true; switch (mode) { case EuclidianConstants.MODE_CAS_EVALUATE: case EuclidianConstants.MODE_CAS_NUMERIC: case EuclidianConstants.MODE_CAS_KEEP_INPUT: // no parameters, keep mode backToEvaluate = false; processInput(command, focus); break; case EuclidianConstants.MODE_CAS_EXPAND: case EuclidianConstants.MODE_CAS_FACTOR: case EuclidianConstants.MODE_CAS_SUBSTITUTE: case EuclidianConstants.MODE_CAS_NUMERICAL_SOLVE: case EuclidianConstants.MODE_CAS_SOLVE: // no parameters processInput(command, focus); break; case EuclidianConstants.MODE_DELETE: // make sure we don't switch to evaluate if delete tool is used in // EV if (getApp().getGuiManager() != null && getApp().getGuiManager() .getActiveToolbarId() != this.getViewID()) { backToEvaluate = false; } boolean undo = deleteCasCells(getConsoleTable().getSelectedRows()); if (undo) { getConsoleTable().getApplication().storeUndoInfo(); } break; case EuclidianConstants.MODE_FUNCTION_INSPECTOR: // make sure we don't switch to evaluate if delete tool is used in // EV if (getApp().getGuiManager() != null && getApp().getGuiManager() .getActiveToolbarId() != this.getViewID()) { backToEvaluate = false; } if (getConsoleTable().getSelectedRows().length > 0) { GeoCasCell cell = getConsoleTable() .getGeoCasCell(getConsoleTable().getSelectedRows()[0]); if (cell != null && cell.getTwinGeo() instanceof GeoFunction) { this.getApp().getDialogManager().showFunctionInspector( (GeoFunction) cell.getTwinGeo()); } } break; case EuclidianConstants.MODE_CAS_DERIVATIVE: case EuclidianConstants.MODE_CAS_INTEGRAL: processInput(command, focus); break; default: backToEvaluate = false; // ignore other modes } if (backToEvaluate) { getApp().setMode(EuclidianConstants.MODE_CAS_EVALUATE, ModeSetter.CAS_VIEW); getApp().closePopups(); } } /** * @param mode * show tooltip for given mode */ protected void showTooltip(int mode) { // only in web } /** * Renames function definitions in the CAS */ @Override public void rename(GeoElement geo) { update(geo); } @Override public void clearView() { // delete all rows getConsoleTable().deleteAllRows(); ensureOneEmptyRow(); if (getConsoleTable().hasEditor()) { getConsoleTable().getEditor().clearInputText(); } } /** * Makes sure we have an empty row at the end. */ public void ensureOneEmptyRow() { int rows = getRowCount(); // add an empty one when we have no rows or last one is not empty or the // last is in construction list if (rows == 0 || !isRowOutputEmpty(rows - 1) || getConsoleTable() .getGeoCasCell(rows - 1).isInConstructionList()) { GeoCasCell casCell = new GeoCasCell(kernel.getConstruction()); getConsoleTable().insertRow(rows, casCell, false); } } /** * Attaches this view to kernel to receive messages */ public void attachView() { clearView(); kernel.notifyAddAll(this); kernel.attach(this); } /** * Detach this view from kernel */ public void detachView() { kernel.detach(this); clearView(); } @Override public void reset() { repaintView(); } @Override public void updateAuxiliaryObject(GeoElement geo) { // do nothing } /** * Defines new functions in the CAS */ @Override public void add(GeoElement geo) { update(geo); ensureOneEmptyRow(); } @Override public void updatePreviewFromInputBar(GeoElement[] geos) { // TODO } /** * Removes function definitions from the CAS */ @Override public void remove(GeoElement geo) { if (geo instanceof GeoCasCell) { GeoCasCell casCell = (GeoCasCell) geo; int row = casCell.getRowNumber(); if (row < 0) { return; } casCell.resetRowNumber(); // we must stop editing here, otherwise content of deleted cell is // copied below boolean wasEditing = getConsoleTable().isEditing(); getConsoleTable().stopEditing(); getConsoleTable().deleteRow(row); if (wasEditing) { getConsoleTable().startEditingRow(row); } } } /** * Handles updates of geo in CAS view. */ @Override public void update(GeoElement geo) { if (geo instanceof GeoCasCell) { GeoCasCell casCell = (GeoCasCell) geo; if (casCell.getRowNumber() < 0) { casCell.reloadRowNumber(); } getConsoleTable().setRow(casCell.getRowNumber(), casCell); } } @Override final public void updateVisualStyle(GeoElement geo, GProperty prop) { update(geo); } /** * Process currently selected cell using the given command and parameters, * e.g. "Integral", [ "x" ] * * @param ggbcmd * command name * @param focus * whether the view should keep focus afterwards */ public void processInput(String ggbcmd, boolean focus) { getApp().getCommandDictionaryCAS(); // #5456 make sure we have the right // dict // before evaluating getInputHandler().processCurrentRow(ggbcmd, focus); getApp().storeUndoInfo(); } /** * Processes given row. * * @see CASInputHandler#processRowThenEdit(int, boolean) * @param row * row index */ public void processRowThenEdit(int row) { getInputHandler().processRowThenEdit(row, true); } /** * Resolves both static (#) and dynamic($) expressions in selected row, * replacement of dynamic references is also done statically. * * @param inputExp * input row * @param row * row the expression is in * @return string with replaced references */ public String resolveCASrowReferences(String inputExp, int row) { String result = getInputHandler().resolveCASrowReferences(inputExp, row, GeoCasCell.ROW_REFERENCE_STATIC, false); return getInputHandler().resolveCASrowReferences(result, row, GeoCasCell.ROW_REFERENCE_DYNAMIC, false); } /** * Deletes given CAS cells both from view and from construction * * @param selRows * selected rows * @return true if undo needed */ public boolean deleteCasCells(int[] selRows) { boolean undoNeeded = false; Log.debug(selRows.length); // reverse order makes sure we don't move cells that are waiting for // deletion for (int i = selRows.length - 1; i >= 0; i--) { GeoCasCell casCell = getConsoleTable().getGeoCasCell(selRows[i]); if (casCell != null) { casCell.remove(); undoNeeded = true; } } if (selRows.length > 0) { getConsoleTable().resetRowNumbers(selRows[0]); } return undoNeeded; } /** * returns latex from selected cells * * @param selRows * selected rows * @return LaTeX for cells, separated by \\ */ public String getLaTeXfromCells(int[] selRows) { StringBuilder ret = new StringBuilder(); for (int i = 0; i < selRows.length; i++) { GeoCasCell casCell = getConsoleTable().getGeoCasCell(selRows[i]); if (casCell != null) { ret.append(casCell.getLaTeXOutput()); // LaTeX linebreak ret.append(" \\\\ "); } } return ret.toString(); } /** * @param selRows * list of rows * @return \n separated cell outputs */ public final String getTextFromCells(int[] selRows) { StringBuilder ret = new StringBuilder(); for (int i = 0; i < selRows.length; i++) { GeoCasCell casCell = getConsoleTable().getGeoCasCell(selRows[i]); if (casCell != null) { ret.append(casCell.getOutput(StringTemplate.casCopyTemplate)); // LaTeX linebreak if (i != selRows.length - 1) { ret.append("\n "); } } } return ret.toString(); } /** * @return input handler */ public CASInputHandler getInputHandler() { return casInputHandler; } /** * @param row * row index (starting from 0) * @return true if given cell is empty */ public boolean isRowEmpty(int row) { if (row < 0) { return false; } GeoCasCell value = getConsoleTable().getGeoCasCell(row); return value.isEmpty(); } /** * @param row * row index (starting from 0) * @return true if given cell's input is empty */ public boolean isRowInputEmpty(int row) { if (row < 0) { return false; } GeoCasCell value = getConsoleTable().getGeoCasCell(row); return value.isInputEmpty(); } /** * Inserts a row at the end and starts editing the new row. * * @param newValue * CAS cell to be added * @param startEditing * true to start editing */ public void insertRow(GeoCasCell newValue, boolean startEditing) { GeoCasCell toInsert = newValue; int lastRow = getRowCount() - 1; if (isRowEmpty(lastRow)) { if (toInsert == null) { toInsert = new GeoCasCell(kernel.getConstruction()); // kernel.getConstruction().setCasCellRow(newValue, lastRow); } getConsoleTable().setRow(lastRow, toInsert); if (startEditing) { getConsoleTable().startEditingRow(lastRow); } } else { getConsoleTable().insertRow(lastRow + 1, toInsert, startEditing); } } /** * @param row * row index (starting from 0) * @return true if given cell is empty and it's not a text cell */ public boolean isRowOutputEmpty(int row) { if (row < 0) { return false; } GeoCasCell value = getConsoleTable().getGeoCasCell(row); return value.isOutputEmpty() && !value.isUseAsText(); } @Override public void startBatchUpdate() { // TODO Auto-generated method stub } @Override public void endBatchUpdate() { // TODO Auto-generated method stub } /** * @param i * cell index * @return input */ public String getCellInput(int i) { GeoCasCell casCell = getConsoleTable().getGeoCasCell(i); if (casCell != null) { return casCell.getInput(StringTemplate.xmlTemplate); } return null; } @Override public void cancelEditItem() { CASTable table = getConsoleTable(); table.stopEditing(); } /** * @param casInputHandler * input handler */ protected void setCasInputHandler(CASInputHandler casInputHandler) { this.casInputHandler = casInputHandler; } }