package org.geogebra.web.web.cas.view; import org.geogebra.common.awt.GPoint; import org.geogebra.common.cas.view.CASTableCellController; import org.geogebra.common.cas.view.CASTableCellEditor; import org.geogebra.common.euclidian.event.KeyEvent; import org.geogebra.common.euclidian.event.KeyHandler; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.main.App; import org.geogebra.common.util.debug.Log; import org.geogebra.web.html5.gui.GuiManagerInterfaceW; import org.geogebra.web.html5.gui.inputfield.AutoCompleteTextFieldW; import org.geogebra.web.html5.gui.util.CancelEventTimer; import org.geogebra.web.html5.gui.util.LongTouchManager; import org.geogebra.web.html5.gui.util.LongTouchTimer.LongTouchHandler; import org.geogebra.web.html5.main.AppW; import org.geogebra.web.html5.util.EventUtil; import org.geogebra.web.web.gui.GuiManagerW; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.HumanInputEvent; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.dom.client.TouchEndEvent; import com.google.gwt.event.dom.client.TouchEndHandler; import com.google.gwt.event.dom.client.TouchMoveEvent; import com.google.gwt.event.dom.client.TouchMoveHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.HTMLTable.Cell; import com.google.gwt.user.client.ui.Widget; /** * HTML5 version of CAS controller * */ public class CASTableControllerW extends CASTableCellController implements MouseDownHandler, MouseUpHandler, MouseMoveHandler, KeyHandler, BlurHandler, TouchStartHandler, TouchEndHandler, TouchMoveHandler, LongTouchHandler { private CASViewW view; private AppW app; private int startSelectRow; private LongTouchManager longTouchManager; private boolean mouseDown; private boolean touchDown; private boolean contextOpened; /** * @param casViewW * cas view * @param app * application */ public CASTableControllerW(CASViewW casViewW, AppW app) { view = casViewW; this.app = app; longTouchManager = LongTouchManager.getInstance(); } @Override public void handleLongTouch(int x, int y) { CASTableW table = view.getConsoleTable(); if (!table.isSelectedIndex(startSelectRow)) { table.setSelectedRows(startSelectRow, startSelectRow); } if (table.getSelectedRows().length > 0) { // TODO select cells for copy RowHeaderPopupMenuW popupMenu = ((GuiManagerW) app.getGuiManager()) .getCASContextMenu(table); popupMenu.show(new GPoint(x, y)); contextOpened = true; } } /** * Sets the toolbar to CAS */ private void setActiveToolbar() { if (app.getToolbar() != null) { GuiManagerInterfaceW gm = app.getGuiManager(); gm.setActivePanelAndToolbar(App.VIEW_CAS); } } /** * Copies the output of a cell into the cell being edited if there is an * editing cell and a cell output was clicked. * * @param event * event * @return true if copying happened */ private boolean copyOutputIfSource(HumanInputEvent<?> event) { if (event.getSource() != view.getComponent()) { CASTableW table = view.getConsoleTable(); CASTableCellW clickedCell = table.getCasCellForEvent(event); return copyOutputToEditingCell(clickedCell); } return false; } private boolean copyOutputToEditingCell(CASTableCellW clickedCell) { CASTableW table = view.getConsoleTable(); CASTableCellW editingCell = table.getEditingCell(); if (editingCell != null && clickedCell != null) { editingCell.insertInput(clickedCell.getOutputString()); return true; } return false; } @Override public void onMouseMove(MouseMoveEvent event) { handleMouseMoveSelection(event); event.stopPropagation(); } @Override public void onMouseUp(MouseUpEvent event) { if (CancelEventTimer.cancelMouseEvent()) { return; } mouseDown = false; CASTableW table = view.getConsoleTable(); GPoint point = table.getPointForEvent(event); if (checkHeaderClick(event)) { // do this even if left/right click, even if clipboard is not // supported! Widget wid = table.getWidget(point.y, point.x); if ((wid != null) && (wid instanceof RowHeaderWidget)) { // quick implementation would call the handler if (((RowHeaderWidget) wid).getHandler() != null) { ((RowHeaderWidget) wid).getHandler().onMouseUp(event); } } } else if (event.getNativeEvent().getButton() == NativeEvent.BUTTON_RIGHT) { // in theory, checkHeaderClick(event) is already false here... // only do this action when supported // only makes sense for mouse events yet // TODO: add this functionality to touch events, // maybe override onPointerUp?? if (!table.isSelectedIndex(point.y)) { table.setSelectedRows(point.y, point.y); } // CASTableCellEditor tableCellEditor = table.getEditor(); RowHeaderPopupMenuW popupMenu = ((GuiManagerW) app .getGuiManager()).getCASContextMenu(table); popupMenu.show(new GPoint(event.getClientX() + Window.getScrollLeft(), event.getClientY() + Window.getScrollTop())); } else { onPointerUp(event); } event.stopPropagation(); } /** * @param event * mouse event * @return whether column is 0 */ public boolean checkHeaderClick(HumanInputEvent<?> event) { CASTableW cw = view.getConsoleTable(); GPoint gp = cw.getPointForEvent(event); if (gp != null && gp.x == 0) { return true; } return false; } /** * @param event * mouse event * @return whether input of a cell was clicked */ public boolean checkQuestionClick(HumanInputEvent<?> event) { CASTableW cw = view.getConsoleTable(); GPoint gp = cw.getPointForEvent(event); if (gp == null || gp.x == 0) { return false; } CASTableCellW ctw = cw.getCasCellForEvent(event); Widget output = ctw.getOutputWidget(); int x = event.getNativeEvent().getClientX(); int y = event.getNativeEvent().getClientY(); if ((output.getAbsoluteLeft() <= x) && (output.getAbsoluteLeft() + output.getOffsetWidth() >= x) && (output.getAbsoluteTop() <= y) && (output.getAbsoluteTop() + output.getOffsetHeight() >= y)) { // this is an "answer" click! return false; } // supposing there is nothing else just the // "header" click, "answer" click or "question" click return true; } @Override public void onMouseDown(MouseDownEvent event) { event.stopPropagation(); if (CancelEventTimer.cancelMouseEvent()) { return; } mouseDown = true; handleMouseDownSelection(event); onPointerDown(); } private void onPointerDown() { setActiveToolbar(); ((GuiManagerW) app.getGuiManager()).removePopup(); boolean oldFocus = ((CASTableCellEditor) view.getEditor()).hasFocus(); app.closePopups(); if (oldFocus) { view.getEditor().setFocus(true, false); } } private void onPointerUp(HumanInputEvent<?> event) { if (copyOutputIfSource(event)) { event.stopPropagation(); return; } CASTableW table = view.getConsoleTable(); Cell cell = table.getCellForEvent(event); table.setFirstRowFront(false); if (cell == null) { return; } if (cell.getCellIndex() == CASTableW.COL_CAS_CELLS_WEB) { int rowIndex = cell.getRowIndex(); table.startEditingRow(rowIndex); } } @Override public void onTouchMove(TouchMoveEvent event) { CASTableW table = view.getConsoleTable(); GPoint point = table.getPointForEvent(event); if (point == null || startSelectRow < 0) { longTouchManager.cancelTimer(); return; } longTouchManager.rescheduleTimerIfRunning(this, EventUtil.getTouchOrClickClientX(event), EventUtil.getTouchOrClickClientY(event)); handleTouchMoveSelection(event); CancelEventTimer.touchEventOccured(); } @Override public void onTouchEnd(TouchEndEvent event) { longTouchManager.cancelTimer(); touchDown = false; if (!contextOpened) { onPointerUp(event); } else { contextOpened = false; } CancelEventTimer.touchEventOccured(); } @Override public void onTouchStart(TouchStartEvent event) { event.stopPropagation(); handleTouchStartSelection(event); touchDown = true; longTouchManager.scheduleTimer(this, EventUtil.getTouchOrClickClientX(event), EventUtil.getTouchOrClickClientY(event)); onPointerDown(); CancelEventTimer.touchEventOccured(); } private void handleMouseDownSelection(MouseDownEvent event) { CASTableW table = view.getConsoleTable(); GPoint point = table.getPointForEvent(event); if (point == null || point.getX() != CASTableW.COL_CAS_HEADER) { startSelectRow = -1; return; } int currentRow = point.getY(); if (event.getNativeButton() == NativeEvent.BUTTON_RIGHT && selectionContainsRow(currentRow)) { // do nothing } else if (event.isShiftKeyDown()) { table.setSelectedRows(startSelectRow, currentRow); } else if (event.isControlKeyDown()) { table.addSelectedRows(currentRow, currentRow); } else { startSelectRow = currentRow; table.setSelectedRows(currentRow, currentRow); } } private boolean selectionContainsRow(int row) { CASTableW table = view.getConsoleTable(); for (Integer item : table.getSelectedRows()) { if (item.equals(row)) { return true; } } return false; } private void handleMouseMoveSelection(MouseMoveEvent event) { CASTableW table = view.getConsoleTable(); GPoint point = table.getPointForEvent(event); if (point == null || point.getX() != CASTableW.COL_CAS_HEADER || startSelectRow < 0 || !mouseDown) { return; } int currentRow = point.getY(); if (event.isControlKeyDown()) { table.setSelectedRows(startSelectRow, currentRow); } else { table.addSelectedRows(currentRow, currentRow); } } private void handleTouchStartSelection(TouchStartEvent event) { CASTableW table = view.getConsoleTable(); GPoint point = table.getPointForEvent(event); if (point == null) { this.startSelectRow = -1; return; } int currentRow = point.getY(); startSelectRow = currentRow; table.setSelectedRows(currentRow, currentRow); } private void handleTouchMoveSelection(TouchMoveEvent event) { CASTableW table = view.getConsoleTable(); GPoint point = table.getPointForEvent(event); if (point == null || startSelectRow < 0 || !touchDown) { return; } int currentRow = point.getY(); table.setSelectedRows(startSelectRow, currentRow); } @Override public void keyReleased(KeyEvent e) { char ch = e.getCharCode(); CASTableW table = view.getConsoleTable(); int editingRow = table.getEditingRow(); if (editingRow < 0) { Log.debug("Key " + ch + " pressed, no row is being edited."); return; } CASTableCellEditor editor = table.getEditor(); String text = editor.getInput(); // if closing paranthesis is typed and there is no opening parenthesis // for it // add one in the beginning if (editingRow > 0 && text.length() == 0) { if (handleFirstLetter(ch, editingRow, editor)) { e.preventDefault(); } } if (e.isEnterKey()) { this.handleEnterKey(e.isCtrlDown(), e.isAltDown(), app, true); e.preventDefault(); } } /** * @param ch * first character * @param editingRow * row index * @param editor * editor * @return true if special handling was necessary */ public boolean handleFirstLetter(char ch, int editingRow, CASTableCellEditor editor) { switch (ch) { case ' ': case '|': // insert output of previous row (not in parentheses) GeoCasCell selCellValue = view.getConsoleTable().getGeoCasCell( editingRow - 1); editor.setInput(selCellValue .getOutputRHS(StringTemplate.defaultTemplate) + " "); return true; case ')': // insert output of previous row in parentheses selCellValue = view.getConsoleTable().getGeoCasCell( editingRow - 1); String prevOutput = selCellValue .getOutputRHS(StringTemplate.defaultTemplate); editor.setInput("(" + prevOutput + ")"); return true; case '=': // insert input of previous row selCellValue = view.getConsoleTable().getGeoCasCell( editingRow - 1); editor.setInput(selCellValue .getInput(StringTemplate.defaultTemplate)); return true; } return false; } @Override public void onBlur(BlurEvent event) { CASTableCellEditor editor = view.getConsoleTable().getEditor(); if (!((CASEditorW) editor).isSuggesting() && !AutoCompleteTextFieldW.isShowSymbolButtonFocused()) { view.getConsoleTable().stopEditing(); view.getConsoleTable().setFirstRowFront(false); } } }