private void moveWithinGridRange(JGrid grid, int dx, int dy, boolean changeLead) {
   if (changeLead) {
     leadRow = clipToRange(leadRow + dy, 0, grid.getRowCount());
     leadColumn = clipToRange(leadColumn + dx, 0, grid.getColumnCount());
   } else {
     anchorRow = clipToRange(anchorRow + dy, 0, grid.getRowCount());
     anchorColumn = clipToRange(anchorColumn + dx, 0, grid.getColumnCount());
   }
 }
    public void actionPerformed(ActionEvent e) {
      JGrid grid = (JGrid) e.getSource();
      if (toLimit) {
        if (vertically) {
          int rowCount = grid.getRowCount();
          this.dx = 0;
          this.dy = forwards ? rowCount : -rowCount;
        } else {
          int colCount = grid.getColumnCount();
          this.dx = forwards ? colCount : -colCount;
          this.dy = 0;
        }
      } else {
        if (!(grid.getParent().getParent() instanceof JScrollPane)) {
          return;
        }

        Dimension delta = grid.getParent().getSize();
        SelectionModel sm = grid.getSelectionModel();

        int start = 0;
        if (vertically) {
          start = (extend) ? sm.getLeadRow() : sm.getAnchorRow();
        } else {
          start = (extend) ? sm.getLeadColumn() : sm.getAnchorColumn();
        }

        if (vertically) {
          Rectangle r = grid.getCellBounds(start, 0);
          r.y += forwards ? delta.height : -delta.height;
          this.dx = 0;
          int newRow = grid.rowAtPoint(r.getLocation());
          if (newRow == -1 && forwards) {
            newRow = grid.getRowCount();
          }
          this.dy = newRow - start;
        } else {
          Rectangle r = grid.getCellBounds(0, start);
          r.x += forwards ? delta.width : -delta.width;
          int newColumn = grid.columnAtPoint(r.getLocation());
          if (newColumn == -1 && forwards) {
            newColumn = grid.getColumnCount();
          }
          this.dx = newColumn - start;
          this.dy = 0;
        }
      }
      super.actionPerformed(e);
    }
    private boolean moveWithinSelectedRange(JGrid grid, int dx, int dy, boolean ignoreCarry) {
      SelectionModel sm = grid.getSelectionModel();

      int newAnchorRow = anchorRow + dy;
      int newAnchorColumn = anchorColumn + dx;

      int rowSgn;
      int colSgn;
      int rowCount = selectionSpan(sm, SwingConstants.VERTICAL);
      int columnCount = selectionSpan(sm, SwingConstants.HORIZONTAL);

      boolean canStayInSelection = (rowCount * columnCount > 1);
      if (canStayInSelection) {
        rowSgn = compare(newAnchorRow, sm, SwingConstants.VERTICAL);
        colSgn = compare(newAnchorColumn, sm, SwingConstants.HORIZONTAL);
      } else {
        // If there is only one selected cell, there is no point
        // in trying to stay within the selected area. Move outside
        // the selection, wrapping at the table boundaries.
        rowCount = grid.getRowCount();
        columnCount = grid.getColumnCount();
        rowSgn = compare(newAnchorRow, 0, rowCount);
        colSgn = compare(newAnchorColumn, 0, columnCount);
      }

      anchorRow = newAnchorRow - rowCount * rowSgn;
      anchorColumn = newAnchorColumn - columnCount * colSgn;

      if (!ignoreCarry) {
        return moveWithinSelectedRange(grid, rowSgn, colSgn, true);
      }
      return canStayInSelection;
    }
  public void paint(Graphics g, JComponent c) {
    if (grid.getRowCount() <= 0 || grid.getColumnCount() <= 0) {
      return; // nothing to paint
    }

    Rectangle clip = g.getClipBounds();
    Point minLocation = clip.getLocation();
    Point maxLocation = new Point(clip.x + clip.width - 1, clip.y + clip.height - 1);
    int rowMin = grid.rowAtPoint(minLocation);
    int rowMax = grid.rowAtPoint(maxLocation);
    // This should never happen.
    if (rowMin == -1) {
      rowMin = 0;
    }
    // If the spread does not have enough rows to fill the view we'll get -1.
    // Replace this with the index of the last row.
    if (rowMax == -1) {
      rowMax = grid.getRowCount() - 1;
    }
    int colMin = grid.columnAtPoint(minLocation);
    int colMax = grid.columnAtPoint(maxLocation);
    // This should never happen.
    if (colMin == -1) {
      colMin = 0;
    }
    // If the spread does not have enough columns to fill the view we'll get -1.
    // Replace this with the index of the last column.
    if (colMax == -1) {
      colMax = grid.getColumnCount() - 1;
    }

    // Paint cells
    paintCells(g, rowMin, rowMax, colMin, colMax);

    // Paint grid
    paintGrid(g, rowMin, rowMax, colMin, colMax);

    // Paint spans
    paintSpans(g, rowMin, rowMax, colMin, colMax);

    // Paint borders
    paintBorders(g, rowMin, rowMax, colMin, colMax);

    // Paint editor
    paintEditor(g);
  }
  public Dimension getMaximumSize(JComponent c) {
    int lastRow = grid.getRowCount() - 1;
    int lastCol = grid.getColumnCount() - 1;

    Rectangle cellBounds = grid.getCellBounds(lastRow, lastCol);
    int width = cellBounds.x + cellBounds.width;
    int height = cellBounds.y + cellBounds.height;

    return new Dimension(width, height);
  }
 /*
  * Borders are handled by the UI since they are shared between cells
  * and therefore must overlay after the painting of the cells
  */
 private void paintBorders(Graphics g, int rowMin, int rowMax, int colMin, int colMax) {
   // Include borders from adjacent rows/columns of the clip region
   rowMin = Math.max(0, rowMin - 1);
   rowMax = Math.min(grid.getRowCount() - 1, rowMax + 1);
   colMin = Math.max(0, colMin - 1);
   colMax = Math.min(grid.getColumnCount() - 1, colMax + 1);
   for (int row = rowMin; row <= rowMax; row++) {
     for (int column = colMin; column <= colMax; column++) {
       Rectangle cellRect = grid.getCellBounds(row, column);
       paintBorder(g, cellRect, row, column);
     }
   }
 }