package org.geogebra.desktop.gui.view.spreadsheet; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import javax.swing.JPopupMenu; import javax.swing.table.DefaultTableModel; import javax.swing.text.JTextComponent; import org.geogebra.common.awt.GPoint; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.euclidian.event.AbstractEvent; import org.geogebra.common.gui.view.spreadsheet.CellRange; import org.geogebra.common.gui.view.spreadsheet.MyTable; import org.geogebra.common.gui.view.spreadsheet.MyTableInterface; import org.geogebra.common.gui.view.spreadsheet.RelativeCopy; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElementSpreadsheet; import org.geogebra.common.main.App; import org.geogebra.common.plugin.GeoClass; import org.geogebra.desktop.euclidian.event.MouseEventD; import org.geogebra.desktop.gui.layout.LayoutD; import org.geogebra.desktop.main.AppD; import com.google.gwt.regexp.shared.MatchResult; public class SpreadsheetMouseListenerD implements MouseListener, MouseMotionListener { protected String selectedCellName; protected String prefix0, postfix0; private AppD app; private SpreadsheetViewD view; private Kernel kernel; private MyTableD table; private DefaultTableModel model; private MyCellEditorSpreadsheet editor; private RelativeCopy relativeCopy; /************************************************* * Constructor */ public SpreadsheetMouseListenerD(AppD app, MyTableD table) { this.app = app; this.kernel = app.getKernel(); this.table = table; this.view = table.getView(); this.model = (DefaultTableModel) table.getModel(); this.editor = table.editor; this.relativeCopy = new RelativeCopy(kernel); } @Override public void mouseClicked(MouseEvent e) { boolean doubleClick = (e.getClickCount() != 1); GPoint point = table.getIndexFromPixel(e.getX(), e.getY()); if (point != null) { if (doubleClick) { // auto-fill down if dragging dot is double-clicked if (table.isOverDot) { handleAutoFillDown(); return; } // otherwise, doubleClick edits cell if (!table.isEditing() && !(table.getOneClickEditMap().containsKey(point) && view.allowSpecialEditor())) { table.setAllowEditing(true); table.editCellAt(table.getSelectedRow(), table.getSelectedColumn()); // workaround, see // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4192625 final JTextComponent f = (JTextComponent) table .getEditorComponent(); if (f != null) { f.requestFocus(); f.getCaret().setVisible(true); } table.setAllowEditing(false); } } } if (editor.isEditing()) { String text = editor.getEditingValue(); if (text.startsWith("=")) { point = table.getIndexFromPixel(e.getX(), e.getY()); if (point != null) { int column = point.getX(); int row = point.getY(); GeoElement geo = RelativeCopy.getValue(app, column, row); if (geo != null) { e.consume(); } } } selectedCellName = null; prefix0 = null; table.isDragging2 = false; table.repaint(); } else if (app .getMode() != EuclidianConstants.MODE_SELECTION_LISTENER) { int row = table.rowAtPoint(e.getPoint()); int col = table.columnAtPoint(e.getPoint()); GeoElement geo = (GeoElement) model.getValueAt(row, col); // let euclidianView know about the click AbstractEvent event = MouseEventD.wrapEvent(e); app.getActiveEuclidianView().clickedGeo(geo, app.isControlDown(event)); event.release(); } // else // { // !editor.isEditing() // int row = rowAtPoint(e.getPoint()); // int col = columnAtPoint(e.getPoint()); // GeoElement geo = (GeoElement) getModel().getValueAt(row, col); // // // copy description into input bar when a cell is clicked on // copyDefinitionToInputBar(geo); // selectionChanged(); // } } /** automatic fill down from the dragging dot */ public void handleAutoFillDown() { int col = table.getSelectedColumn(); int row = table.maxSelectionRow; if (model.getValueAt(row, col) != null) { // count nonempty cells below selection // if no cells below, count left ... if none on the left, count // right while (row < table.getRowCount() - 1 && model.getValueAt(row + 1, col) != null) { row++; } if (row - table.maxSelectionRow == 0 && col > 0) { while (row < table.getRowCount() - 1 && model.getValueAt(row + 1, col - 1) != null) { row++; } } if (row - table.maxSelectionRow == 0 && table.maxSelectionColumn <= table.getColumnCount() - 1) { while (row < table.getRowCount() - 1 && model.getValueAt(row + 1, table.maxSelectionColumn + 1) != null) { row++; } } int rowCount = row - table.maxSelectionRow; // now fill down if (rowCount != 0) { boolean succ = relativeCopy.doDragCopy(table.minSelectionColumn, table.minSelectionRow, table.maxSelectionColumn, table.maxSelectionRow, table.minSelectionColumn, table.maxSelectionRow + 1, table.maxSelectionColumn, table.maxSelectionRow + rowCount); if (succ) { app.storeUndoInfo(); } } table.isDragingDot = false; } } @Override public void mouseEntered(MouseEvent e) { // nothing to do } @Override public void mouseExited(MouseEvent e) { // nothing to do } @Override public void mousePressed(MouseEvent e) { boolean rightClick = AppD.isRightClick(e); if (!view.hasViewFocus()) { ((LayoutD) app.getGuiManager().getLayout()).getDockManager() .setFocusedPanel(App.VIEW_SPREADSHEET); } // tell selection listener about click on GeoElement if (!rightClick && app .getMode() == EuclidianConstants.MODE_SELECTION_LISTENER) { int row = table.rowAtPoint(e.getPoint()); int col = table.columnAtPoint(e.getPoint()); GeoElement geo = (GeoElement) model.getValueAt(row, col); // double click or empty geo if (e.getClickCount() == 2 || geo == null) { table.requestFocusInWindow(); } else { // tell selection listener about click app.geoElementSelected(geo, false); e.consume(); return; } } if (!rightClick) { // memory testing // Application.debug("", true, true, 0); if (table.getSelectionType() != MyTableInterface.CELL_SELECT) { table.setSelectionType(MyTableInterface.CELL_SELECT); } // force column selection if (view.isColumnSelect()) { GPoint point = table.getIndexFromPixel(e.getX(), e.getY()); if (point != null) { int column = point.getX(); table.setColumnSelectionInterval(column, column); } } /* * if (MyTable.this.getSelectionModel().getSelectionMode() != * ListSelectionModel.SINGLE_INTERVAL_SELECTION) { * setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); * setColumnSelectionAllowed(true); setRowSelectionAllowed(true); } */ GPoint point1 = table.getMaxSelectionPixel(); if (point1 == null) { return; } // Handle click in another cell while editing a cell: // if the edit string begins with "=" then the clicked cell name // is inserted into the edit text if (editor.isEditing()) { String text = editor.getEditingValue(); if (text.startsWith("=")) { GPoint point = table.getIndexFromPixel(e.getX(), e.getY()); if (point != null) { int column = point.getX(); int row = point.getY(); GeoElement geo = RelativeCopy.getValue(app, column, row); if (geo != null) { GeoClass geoType = geo.getGeoClassType(); if (geoType == GeoClass.BUTTON || geoType == GeoClass.BOOLEAN) { return; } // get cell name String name = GeoElementSpreadsheet .getSpreadsheetCellName(column, row); if (geo.isGeoFunction()) { name += "(x)"; } selectedCellName = name; // get prefix/post substrings for current text caret // position int caretPos = editor.getCaretPosition(); prefix0 = text.substring(0, caretPos); postfix0 = text.substring(caretPos, text.length()); table.isDragging2 = true; table.minColumn2 = column; table.maxColumn2 = column; table.minRow2 = row; table.maxRow2 = row; // insert the geo label into the editor string editor.addLabel(name); e.consume(); table.repaint(); } e.consume(); } } else { // if text does not start with "=" then stop the editor // and allow it to create/redefine a geo here editor.setAllowProcessGeo(true); editor.stopCellEditing(); editor.setAllowProcessGeo(false); } } else if (table.isOverDot && table.showCanDragBlueDot()) { // double-check table.isDragingDot = true; e.consume(); } } } @Override public void mouseReleased(MouseEvent e) { boolean rightClick = AppD.isRightClick(e); if (table.getTableMode() == MyTable.TABLE_MODE_AUTOFUNCTION) { table.getSpreadsheetModeProcessor().stopAutoFunction(); return; } if (!rightClick) { if (editor.isEditing()) { String text = editor.getEditingValue(); if (text.startsWith("=")) { GPoint point = table.getIndexFromPixel(e.getX(), e.getY()); if (point != null) { int column = point.getX(); int row = point.getY(); if (column != editor.column || row != editor.row) { e.consume(); } } } selectedCellName = null; prefix0 = null; postfix0 = null; table.isDragging2 = false; table.repaint(); } if (table.isOverDot) { // prevent UI manager from changing selection when mouse // is in a neighbor cell but is still over the dot region e.consume(); } if (table.isDragingDot) { if (table.dragingToColumn == -1 || table.dragingToRow == -1) { return; } int x1 = -1; int y1 = -1; int x2 = -1; int y2 = -1; // -|1|- // 2|-|3 // -|4|- if (table.dragingToColumn < table.minSelectionColumn) { // 2 x1 = table.dragingToColumn; y1 = table.minSelectionRow; x2 = table.minSelectionColumn - 1; y2 = table.maxSelectionRow; } else if (table.dragingToRow > table.maxSelectionRow) { // 4 x1 = table.minSelectionColumn; y1 = table.maxSelectionRow + 1; x2 = table.maxSelectionColumn; y2 = table.dragingToRow; } else if (table.dragingToRow < table.minSelectionRow) { // 1 x1 = table.minSelectionColumn; y1 = table.dragingToRow; x2 = table.maxSelectionColumn; y2 = table.minSelectionRow - 1; } else if (table.dragingToColumn > table.maxSelectionColumn) { // 3 x1 = table.maxSelectionColumn + 1; y1 = table.minSelectionRow; x2 = table.dragingToColumn; y2 = table.maxSelectionRow; } // copy the cells boolean succ = relativeCopy.doDragCopy(table.minSelectionColumn, table.minSelectionRow, table.maxSelectionColumn, table.maxSelectionRow, x1, y1, x2, y2); if (succ) { app.storeUndoInfo(); } // extend the selection to include the drag copy selection table.setSelection(Math.min(x1, table.minSelectionColumn), Math.min(y1, table.minSelectionRow), Math.max(x2, table.maxSelectionColumn), Math.max(y2, table.maxSelectionRow)); // reset flags and cursor table.isOverDot = false; table.isDragingDot = false; table.dragingToRow = -1; table.dragingToColumn = -1; setTableCursor(); // prevent UI manager from changing selection e.consume(); table.repaint(); } } // Alt click: copy definition to input field if (!table.isEditing() && e.isAltDown() && app.showAlgebraInput()) { int row = table.rowAtPoint(e.getPoint()); int col = table.columnAtPoint(e.getPoint()); GeoElement geo = (GeoElement) model.getValueAt(row, col); if (geo != null) { // F3 key: copy definition to input bar app.getGlobalKeyDispatcher().handleFunctionKeyForAlgebraInput(3, geo); return; } } // handle right click if (rightClick) { if (!((AppD) kernel.getApplication()).letShowPopupMenu()) { return; } GPoint p = table.getIndexFromPixel(e.getX(), e.getY()); // change selection if right click is outside current selection if (p.getY() < table.minSelectionRow || p.getY() > table.maxSelectionRow || p.getX() < table.minSelectionColumn || p.getX() > table.maxSelectionColumn) { // switch to cell selection mode if (table.getSelectionType() != MyTableInterface.CELL_SELECT) { table.setSelectionType(MyTableInterface.CELL_SELECT); } // now change the selection table.changeSelection(p.getY(), p.getX(), false, false); } // create and show context menu SpreadsheetContextMenuD contextMenu = new SpreadsheetContextMenuD( table); JPopupMenu popup = (JPopupMenu) contextMenu.getMenuContainer(); popup.show(e.getComponent(), e.getX(), e.getY()); } } @Override public void mouseDragged(MouseEvent e) { if (table.getTableMode() == MyTable.TABLE_MODE_AUTOFUNCTION || table.getTableMode() == MyTable.TABLE_MODE_DROP) { // System.out.println("drop is dragging "); return; } // handle editing mode drag if (editor.isEditing()) { GPoint point = table.getIndexFromPixel(e.getX(), e.getY()); if (point != null && selectedCellName != null) { int column2 = point.getX(); int row2 = point.getY(); MatchResult matcher = GeoElementSpreadsheet.spreadsheetPattern .exec(selectedCellName); int column1 = GeoElementSpreadsheet .getSpreadsheetColumn(matcher); int row1 = GeoElementSpreadsheet.getSpreadsheetRow(matcher); if (column1 > column2) { int temp = column1; column1 = column2; column2 = temp; } if (row1 > row2) { int temp = row1; row1 = row2; row2 = temp; } String name1 = GeoElementSpreadsheet .getSpreadsheetCellName(column1, row1); String name2 = GeoElementSpreadsheet .getSpreadsheetCellName(column2, row2); if (!name1.equals(name2)) { name1 += ":" + name2; } name1 = prefix0 + name1 + postfix0; editor.setLabel(name1); table.minColumn2 = column1; table.maxColumn2 = column2; table.minRow2 = row1; table.maxRow2 = row2; table.repaint(); } e.consume(); return; } // handle dot drag if (table.isDragingDot) { e.consume(); int mouseX = e.getX(); int mouseY = e.getY(); GPoint mouseCell = table.getIndexFromPixel(mouseX, mouseY); // save the selected cell position so it can be re-selected if // needed CellRange oldSelection = table.getSelectedCellRanges().get(0); if (mouseCell == null) { // user has dragged outside the table, to // left or above table.dragingToRow = -1; table.dragingToColumn = -1; } else { table.dragingToRow = mouseCell.getY(); table.dragingToColumn = mouseCell.getX(); Rectangle selRect = table.getSelectionRect(true); // increase size if we're at the bottom of the spreadsheet if (table.dragingToRow + 1 == table.getRowCount() && table.dragingToRow < app .getMaxSpreadsheetRowsVisible()) { model.setRowCount(table.getRowCount() + 1); } // increase size if we go beyond the right edge if (table.dragingToColumn + 1 == table.getColumnCount() && table.dragingToColumn < app .getMaxSpreadsheetColumnsVisible()) { model.setColumnCount(table.getColumnCount() + 1); view.getColumnHeader().revalidate(); // Java's addColumn method will clear selection, so // re-select our cell table.setSelection(oldSelection); } // scroll to show "highest" selected cell table.scrollRectToVisible( table.getCellRect(mouseCell.y, mouseCell.x, true)); if (!selRect.contains(e.getPoint())) { int rowOffset = 0, colOffset = 0; // get row distance if (table.minSelectionRow > 0 && table.dragingToRow < table.minSelectionRow) { rowOffset = mouseY - selRect.y; if (-rowOffset < 0.5 * table.getCellRect(table.minSelectionRow - 1, table.minSelectionColumn, true).height) { rowOffset = 0; } } else if (table.maxSelectionRow < app .getMaxSpreadsheetRowsVisible() && table.dragingToRow > table.maxSelectionRow) { rowOffset = mouseY - (selRect.y + selRect.height); if (rowOffset < 0.5 * table.getCellRect(table.maxSelectionRow + 1, table.maxSelectionColumn, true).height) { rowOffset = 0; } } // get column distance if (table.minSelectionColumn > 0 && table.dragingToColumn < table.minSelectionColumn) { colOffset = mouseX - selRect.x; if (-colOffset < 0.5 * table.getCellRect(table.minSelectionRow, table.minSelectionColumn - 1, true).width) { colOffset = 0; } } else if (table.maxSelectionColumn < app .getMaxSpreadsheetColumnsVisible() && table.dragingToColumn > table.maxSelectionColumn) { colOffset = mouseX - (selRect.x + selRect.width); if (colOffset < 0.5 * table.getCellRect(table.maxSelectionRow, table.maxSelectionColumn + 1, true).width) { colOffset = 0; } } if (rowOffset == 0 && colOffset == 0) { table.dragingToColumn = -1; table.dragingToRow = -1; } else if (Math.abs(rowOffset) > Math.abs(colOffset)) { table.dragingToRow = mouseCell.y; table.dragingToColumn = (colOffset > 0) ? table.maxSelectionColumn : table.minSelectionColumn; } else { table.dragingToColumn = mouseCell.x; table.dragingToRow = (rowOffset > 0) ? table.maxSelectionRow : table.minSelectionRow; } table.repaint(); } // handle ctrl-select dragging of cell blocks else { if (e.isControlDown()) { table.handleControlDragSelect(e); } } } } } /** * Shows tool tip description of geo on mouse over */ @Override public void mouseMoved(MouseEvent e) { if (table.isEditing()) { return; } // get GeoElement at mouse location int row = table.rowAtPoint(e.getPoint()); int col = table.columnAtPoint(e.getPoint()); GeoElement geo = (GeoElement) model.getValueAt(row, col); // set tooltip with geo's description if (geo != null & view.getAllowToolTips()) { app.getLocalization().setTooltipFlag(); table.setToolTipText(geo.getLongDescriptionHTML(true, true)); app.getLocalization().clearTooltipFlag(); } else { table.setToolTipText(null); } // check if over the dragging dot and update accordingly GPoint maxPoint = table.getMaxSelectionPixel(); GPoint minPoint = table.getMinSelectionPixel(); if (maxPoint != null) { int dotX = maxPoint.getX(); int dotY = maxPoint.getY(); int s = MyTableD.DOT_SIZE + 2; Rectangle dotRect = new Rectangle(dotX - s / 2, dotY - s / 2, s, s); boolean overDot = dotRect.contains(e.getPoint()); if (table.isOverDot != overDot) { table.isOverDot = overDot; if (table.showCanDragBlueDot()) { setTableCursor(); table.repaint(); } } } // check if over the DnD region and update accordingly GPoint testPoint = table.getMinSelectionPixel(); if (testPoint != null) { int minX = minPoint.getX(); int minY = minPoint.getY(); int maxX = maxPoint.getX(); int w = maxX - minX; Rectangle dndRect = new Rectangle(minX, minY - 2, w, 4); boolean overDnD = dndRect.contains(e.getPoint()); if (table.isOverDnDRegion != overDnD) { table.isOverDnDRegion = overDnD; setTableCursor(); } } } /** * Sets table cursor */ private void setTableCursor() { if (table.isOverDot) { table.setCursor(table.crossHairCursor); } else { table.setCursor(table.defaultCursor); } } }