package org.geogebra.desktop.gui.view.spreadsheet; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import javax.swing.AbstractListModel; import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableModel; import org.geogebra.common.awt.GPoint; import org.geogebra.common.gui.view.spreadsheet.MyTableInterface; import org.geogebra.common.main.App; import org.geogebra.desktop.gui.layout.LayoutD; import org.geogebra.desktop.main.AppD; public class SpreadsheetRowHeaderD extends JList implements MouseListener, MouseMotionListener, KeyListener, ListSelectionListener { private static final long serialVersionUID = 1L; private AppD app; private SpreadsheetViewD view; private MyTableD table; private MyListModel listModel; // note: MyTable uses its own minSelectionRow and maxSelectionRow. // The selection listener keeps them in sync. private int minSelectionRow = -1; private int maxSelectionRow = -1; // fields for resizing rows private static Cursor resizeCursor = Cursor .getPredefinedCursor(Cursor.N_RESIZE_CURSOR); private Cursor otherCursor = resizeCursor; private int mouseYOffset, resizingRow; private boolean doRowResize = false; protected int row0 = -1; /*************************************************** * Constructor */ public SpreadsheetRowHeaderD(AppD app, MyTableD table) { this.app = app; this.table = table; this.view = table.getView(); listModel = new MyListModel((DefaultTableModel) table.getModel()); this.setModel(listModel); setFocusable(true); setAutoscrolls(false); addMouseListener(this); addMouseMotionListener(this); addKeyListener(this); setFixedCellWidth(SpreadsheetViewD.ROW_HEADER_WIDTH); setCellRenderer(new RowHeaderRenderer(table, this)); table.getSelectionModel().addListSelectionListener(this); } public static class MyListModel extends AbstractListModel { private static final long serialVersionUID = 1L; protected DefaultTableModel model; public MyListModel(DefaultTableModel model) { this.model = model; } @Override public int getSize() { return model.getRowCount(); } @Override public Object getElementAt(int index) { return "" + (index + 1); } // forces update of rowHeader, called after row resizing public Void changed() { this.fireContentsChanged(this, 0, model.getRowCount()); return null; } } public void updateRowHeader() { listModel.changed(); } // =============================================== // Renderer // =============================================== public class RowHeaderRenderer extends JLabel implements ListCellRenderer { private static final long serialVersionUID = 1L; protected JList rowHeader; private Color defaultBackground; public RowHeaderRenderer(JTable table, JList rowHeader) { super("", SwingConstants.CENTER); setOpaque(true); defaultBackground = MyTableD.BACKGROUND_COLOR_HEADER; this.rowHeader = rowHeader; setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, MyTableD.HEADER_GRID_COLOR)); } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { setFont(app.getPlainFont()); // adjust row height to match spreadsheet table row height Dimension size = getPreferredSize(); size.height = table.getRowHeight(index); setPreferredSize(size); setText((value == null) ? "" : value.toString()); if (table.getSelectionType() == MyTableInterface.COLUMN_SELECT) { setBackground(defaultBackground); } else { if (table.selectedRowSet.contains(index) || (index >= minSelectionRow && index <= maxSelectionRow)) { setBackground(MyTableD.SELECTED_BACKGROUND_COLOR_HEADER); } else { setBackground(defaultBackground); } } return this; } } /** * Update the rowHeader list when row selection changes in the table */ @Override public void valueChanged(ListSelectionEvent e) { ListSelectionModel selectionModel = (ListSelectionModel) e.getSource(); minSelectionRow = selectionModel.getMinSelectionIndex(); maxSelectionRow = selectionModel.getMaxSelectionIndex(); repaint(); } // Returns index of row to be resized if mouse point P is // near a row boundary (within 3 pixels) private int getResizingRow(Point p) { int resizeRow = -1; GPoint point = table.getIndexFromPixel(p.x, p.y); if (point != null) { // test if mouse is 3 pixels from row boundary int cellRow = point.getY(); if (cellRow >= 0) { Rectangle r = table.getCellRect(cellRow, 0, true); // near row bottom if (p.y < r.y + 3) { resizeRow = cellRow - 1; } // near row top if (p.y > r.y + r.height - 3) { resizeRow = cellRow; } } } return resizeRow; } // Cursor change for when mouse is over a row boundary private void swapCursor() { Cursor tmp = getCursor(); setCursor(otherCursor); otherCursor = tmp; } // =============================================== // Mouse Listener Methods // =============================================== @Override public void mouseClicked(MouseEvent e) { // Double clicking on a row boundary auto-adjusts the // height of the row above the boundary (the resizingRow) if (resizingRow >= 0 && !AppD.isRightClick(e) && e.getClickCount() == 2) { table.fitRow(resizingRow); e.consume(); } } @Override public void mouseEntered(MouseEvent e) { // only click } @Override public void mouseExited(MouseEvent e) { // only click } @Override public void mousePressed(MouseEvent e) { boolean shiftPressed = e.isShiftDown(); boolean rightClick = AppD.isRightClick(e); int x = e.getX(); int y = e.getY(); if (!view.hasViewFocus()) { ((LayoutD) app.getGuiManager().getLayout()).getDockManager() .setFocusedPanel(App.VIEW_SPREADSHEET); } // Update resizingRow. If nonnegative, then mouse is over a boundary // and it gives the row to be resized (resizing is done in // mouseDragged). Point p = e.getPoint(); resizingRow = getResizingRow(p); mouseYOffset = p.y - table.getRowHeight(resizingRow); // // left click if (!rightClick) { if (resizingRow >= 0) { return; // GSTURR 2010-1-9 } GPoint point = table.getIndexFromPixel(x, y); if (point != null) { // G.STURR 2010-1-29 if (table.getSelectionType() != MyTableInterface.ROW_SELECT) { table.setSelectionType(MyTableInterface.ROW_SELECT); requestFocusInWindow(); } if (shiftPressed) { if (row0 != -1) { int row = point.getY(); table.setRowSelectionInterval(row0, row); } } // ctrl-select is handled in table else { row0 = point.getY(); table.setRowSelectionInterval(row0, row0); } table.repaint(); } } } @Override public void mouseReleased(MouseEvent e) { boolean rightClick = AppD.isRightClick(e); if (rightClick) { if (!app.letShowPopupMenu()) { return; } GPoint p = table.getIndexFromPixel(e.getX(), e.getY()); if (p == null) { return; } // if click is outside current selection then change selection if (p.getY() < minSelectionRow || p.getY() > maxSelectionRow || p.getX() < table.minSelectionColumn || p.getX() > table.maxSelectionColumn) { // switch to row selection mode and select row if (table.getSelectionType() != MyTableInterface.ROW_SELECT) { table.setSelectionType(MyTableInterface.ROW_SELECT); } table.setRowSelectionInterval(p.getY(), p.getY()); } // show contextMenu SpreadsheetContextMenuD contextMenu = new SpreadsheetContextMenuD( table); JPopupMenu popup = (JPopupMenu) contextMenu.getMenuContainer(); popup.show(e.getComponent(), e.getX(), e.getY()); } // If row resize has happened, resize all other selected rows if (doRowResize) { if (minSelectionRow != -1 && maxSelectionRow != -1 && (maxSelectionRow - minSelectionRow > 1)) { if (table.isSelectAll()) { table.setRowHeight(table.getRowHeight(resizingRow)); } else { for (int row = minSelectionRow; row <= maxSelectionRow; row++) { table.setRowHeight(row, table.getRowHeight(resizingRow)); } } } doRowResize = false; } } // =============================================== // MouseMotion Listener Methods // =============================================== @Override public void mouseDragged(MouseEvent e) { if (AppD.isRightClick(e)) { return; // G.Sturr 2009-9-30 } // G.STURR 2010-1-9 // On mouse drag either resize or select a row int x = e.getX(); int y = e.getY(); if (resizingRow >= 0) { // resize row int newHeight = y - mouseYOffset; if (newHeight > 0) { table.setRowHeight(resizingRow, newHeight); // set this flag to resize all selected rows on mouse release doRowResize = true; } } else { // select row GPoint point = table.getIndexFromPixel(x, y); if (point != null) { int row = point.getY(); table.setRowSelectionInterval(row0, row); // G.Sturr 2010-4-4 // keep the row header updated when drag selecting multiple rows view.updateRowHeader(); table.scrollRectToVisible( table.getCellRect(point.y, point.x, true)); table.repaint(); } } } @Override public void mouseMoved(MouseEvent e) { // Show resize cursor when mouse is over a row boundary if ((getResizingRow( e.getPoint()) >= 0) != (getCursor() == resizeCursor)) { swapCursor(); } } // =============================================== // Key Listener Methods // =============================================== @Override public void keyTyped(KeyEvent e) { // only press } @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); boolean metaDown = AppD.isControlDown(e); boolean altDown = e.isAltDown(); boolean shiftDown = e.isShiftDown(); // Application.debug(keyCode); switch (keyCode) { default: // do nothing break; case KeyEvent.VK_UP: if (shiftDown) { // extend the column selection int row = table.getSelectionModel().getLeadSelectionIndex(); table.changeSelection(row - 1, -1, false, true); } else { // select topmost cell in first column to the left of the // selection if (table.minSelectionRow > 0) { table.setSelection(0, table.minSelectionRow - 1); } else { table.setSelection(0, table.minSelectionRow); } table.requestFocus(); } break; case KeyEvent.VK_DOWN: if (shiftDown) { // extend the column selection int row = table.getSelectionModel().getLeadSelectionIndex(); table.changeSelection(row + 1, -1, false, true); } else { // select topmost cell in first column to the left of the // selection if (table.minSelectionRow > 0) { table.setSelection(0, table.minSelectionRow + 1); } else { table.setSelection(0, table.minSelectionRow); } table.requestFocus(); } break; case KeyEvent.VK_C: // control + c if (metaDown && minSelectionRow != -1 && maxSelectionRow != -1) { table.copyPasteCut.copy(0, minSelectionRow, table.getModel().getColumnCount() - 1, maxSelectionRow, altDown); e.consume(); } break; case KeyEvent.VK_V: // control + v if (metaDown && minSelectionRow != -1 && maxSelectionRow != -1) { boolean storeUndo = table.copyPasteCut.paste(0, minSelectionRow, table.getModel().getColumnCount() - 1, maxSelectionRow); if (storeUndo) { app.storeUndoInfo(); } e.consume(); } break; case KeyEvent.VK_X: // control + x if (metaDown && minSelectionRow != -1 && maxSelectionRow != -1) { table.copyPasteCut.copy(0, minSelectionRow, table.getModel().getColumnCount() - 1, maxSelectionRow, altDown); e.consume(); } boolean storeUndo = table.copyPasteCut.delete(0, minSelectionRow, table.getModel().getColumnCount() - 1, maxSelectionRow); if (storeUndo) { app.storeUndoInfo(); } break; case KeyEvent.VK_DELETE: // delete case KeyEvent.VK_BACK_SPACE: // delete on MAC storeUndo = table.copyPasteCut.delete(0, minSelectionRow, table.getModel().getColumnCount() - 1, maxSelectionRow); if (storeUndo) { app.storeUndoInfo(); } break; } } @Override public void keyReleased(KeyEvent e) { // only press } }