| /* |
| * Copyright (c) 2001, 2002, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| package sun.jvm.hotspot.ui; |
| |
| import java.awt.*; |
| import java.awt.datatransfer.*; |
| import java.awt.event.*; |
| import java.io.IOException; |
| import java.math.*; |
| import java.util.*; |
| import javax.swing.*; |
| import javax.swing.event.*; |
| import javax.swing.table.*; |
| import sun.jvm.hotspot.debugger.*; |
| import sun.jvm.hotspot.ui.*; |
| |
| public class MemoryPanel extends JPanel { |
| private boolean is64Bit; |
| private Debugger debugger; |
| private int addressSize; |
| private String unmappedAddrString; |
| private HighPrecisionJScrollBar scrollBar; |
| private AbstractTableModel model; |
| private JTable table; |
| private BigInteger startVal; |
| // Includes any partially-visible row at the bottom |
| private int numVisibleRows; |
| // Frequently-used subexpression |
| private int numUsableRows; |
| // Multi-row (and multi-column) selection. Have to duplicate state |
| // from UI so this can work as we scroll off the screen. |
| private boolean haveAnchor; |
| private int rowAnchorIndex; |
| private int colAnchorIndex; |
| private boolean haveLead; |
| private int rowLeadIndex; |
| private int colLeadIndex; |
| |
| abstract class ActionWrapper extends AbstractAction { |
| private Action parent; |
| ActionWrapper() { |
| } |
| |
| void setParent(Action parent) { |
| this.parent = parent; |
| } |
| |
| Action getParent() { |
| return parent; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| if (getParent() != null) { |
| getParent().actionPerformed(e); |
| } |
| } |
| } |
| |
| public MemoryPanel(final Debugger debugger, boolean is64Bit) { |
| super(); |
| this.debugger = debugger; |
| this.is64Bit = is64Bit; |
| if (is64Bit) { |
| addressSize = 8; |
| unmappedAddrString = "??????????????????"; |
| } else { |
| addressSize = 4; |
| unmappedAddrString = "??????????"; |
| } |
| setLayout(new BorderLayout()); |
| setupScrollBar(); |
| add(scrollBar, BorderLayout.EAST); |
| |
| model = new AbstractTableModel() { |
| public int getRowCount() { |
| return numVisibleRows; |
| } |
| public int getColumnCount() { |
| return 2; |
| } |
| public Object getValueAt(int row, int column) { |
| switch (column) { |
| case 0: return bigIntToHexString(startVal.add(new BigInteger(Integer.toString((row * addressSize))))); |
| case 1: { |
| try { |
| Address addr = bigIntToAddress(startVal.add(new BigInteger(Integer.toString((row * addressSize))))); |
| if (addr != null) { |
| return addressToString(addr.getAddressAt(0)); |
| } |
| return unmappedAddrString; |
| } catch (UnmappedAddressException e) { |
| return unmappedAddrString; |
| } |
| } |
| default: throw new RuntimeException("Column " + column + " out of bounds"); |
| } |
| } |
| public boolean isCellEditable(int row, int col) { |
| return false; |
| } |
| }; |
| |
| // View with JTable with no header |
| table = new JTable(model); |
| table.setTableHeader(null); |
| table.setShowGrid(false); |
| table.setIntercellSpacing(new Dimension(0, 0)); |
| table.setCellSelectionEnabled(true); |
| table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); |
| table.setDragEnabled(true); |
| Font font = GraphicsUtilities.lookupFont("Courier"); |
| if (font == null) { |
| throw new RuntimeException("Error looking up monospace font Courier"); |
| } |
| table.setFont(font); |
| |
| // Export proper data. |
| // We need to keep our own notion of the selection in order to |
| // properly export data, since the selection can go beyond the |
| // visible area on the screen (and since the table's model doesn't |
| // back all of those slots). |
| // Code thanks to Shannon.Hickey@sfbay |
| table.setTransferHandler(new TransferHandler() { |
| protected Transferable createTransferable(JComponent c) { |
| JTable table = (JTable)c; |
| if (haveSelection()) { |
| StringBuffer buf = new StringBuffer(); |
| int iDir = (getRowAnchor() < getRowLead() ? 1 : -1); |
| int jDir = (getColAnchor() < getColLead() ? 1 : -1); |
| |
| for (int i = getRowAnchor(); i != getRowLead() + iDir; i += iDir) { |
| for (int j = getColAnchor(); j != getColLead() + jDir; j += jDir) { |
| Object val = model.getValueAt(i, j); |
| buf.append(val == null ? "" : val.toString()); |
| if (j != getColLead()) { |
| buf.append("\t"); |
| } |
| } |
| if (i != getRowLead()) { |
| buf.append("\n"); |
| } |
| } |
| |
| return new StringTransferable(buf.toString()); |
| } |
| return null; |
| } |
| |
| public int getSourceActions(JComponent c) { |
| return COPY; |
| } |
| |
| public boolean importData(JComponent c, Transferable t) { |
| if (canImport(c, t.getTransferDataFlavors())) { |
| try { |
| String str = (String)t.getTransferData(DataFlavor.stringFlavor); |
| handleImport(c, str); |
| return true; |
| } catch (UnsupportedFlavorException ufe) { |
| } catch (IOException ioe) { |
| } |
| } |
| |
| return false; |
| } |
| |
| public boolean canImport(JComponent c, DataFlavor[] flavors) { |
| for (int i = 0; i < flavors.length; i++) { |
| if (DataFlavor.stringFlavor.equals(flavors[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void handleImport(JComponent c, String str) { |
| // do whatever you want with the string here |
| try { |
| makeVisible(debugger.parseAddress(str)); |
| clearSelection(); |
| table.clearSelection(); |
| } catch (NumberFormatException e) { |
| System.err.println("Unable to parse address \"" + str + "\""); |
| } |
| } |
| }); |
| |
| // Supporting keyboard scrolling |
| // See src/share/classes/javax/swing/plaf/metal/MetalLookAndFeel.java, |
| // search for Table.AncestorInputMap |
| |
| // Actions to override: |
| // selectPreviousRow, selectNextRow, |
| // scrollUpChangeSelection, scrollDownChangeSelection, |
| // selectPreviousRowExtendSelection, selectNextRowExtendSelection, |
| // scrollDownExtendSelection, scrollUpExtendSelection (Shift-PgDn/PgUp) |
| |
| ActionMap map = table.getActionMap(); |
| |
| // Up arrow |
| installActionWrapper(map, "selectPreviousRow", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| clearSelection(); |
| if (table.getSelectedRow() == 0) { |
| scrollBar.scrollUpOrLeft(); |
| table.setRowSelectionInterval(0, 0); |
| } else { |
| super.actionPerformed(e); |
| } |
| maybeGrabSelection(); |
| endUpdate(); |
| } |
| }); |
| // Down arrow |
| installActionWrapper(map, "selectNextRow", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| clearSelection(); |
| int row = table.getSelectedRow(); |
| if (row >= numUsableRows) { |
| scrollBar.scrollDownOrRight(); |
| table.setRowSelectionInterval(row, row); |
| } else { |
| super.actionPerformed(e); |
| } |
| maybeGrabSelection(); |
| endUpdate(); |
| } |
| }); |
| // Page up |
| installActionWrapper(map, "scrollUpChangeSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| clearSelection(); |
| int row = table.getSelectedRow(); |
| scrollBar.pageUpOrLeft(); |
| if (row >= 0) { |
| table.setRowSelectionInterval(row, row); |
| } |
| maybeGrabSelection(); |
| endUpdate(); |
| } |
| }); |
| // Page down |
| installActionWrapper(map, "scrollDownChangeSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| clearSelection(); |
| int row = table.getSelectedRow(); |
| scrollBar.pageDownOrRight(); |
| if (row >= 0) { |
| table.setRowSelectionInterval(row, row); |
| } |
| maybeGrabSelection(); |
| endUpdate(); |
| } |
| }); |
| // Shift + Up arrow |
| installActionWrapper(map, "selectPreviousRowExtendSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| if (!haveAnchor()) { |
| setAnchorFromTable(); |
| setLeadFromTable(); |
| // setAnchor(table.getSelectedRow()); |
| // setLead(table.getSelectedRow()); |
| } |
| int newLead = getRowLead() - 1; |
| int newAnchor = getRowAnchor(); |
| if (newLead < 0) { |
| scrollBar.scrollUpOrLeft(); |
| ++newLead; |
| ++newAnchor; |
| } |
| setSelection(newAnchor, newLead, getColAnchor(), getColLead()); |
| // printSelection(); |
| endUpdate(); |
| } |
| }); |
| // Shift + Left arrow |
| installActionWrapper(map, "selectPreviousColumnExtendSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| if (!haveAnchor()) { |
| setAnchorFromTable(); |
| setLeadFromTable(); |
| } |
| int newLead = Math.max(0, getColLead() - 1); |
| setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead); |
| // printSelection(); |
| endUpdate(); |
| } |
| }); |
| // Shift + Down arrow |
| installActionWrapper(map, "selectNextRowExtendSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| if (!haveAnchor()) { |
| setAnchorFromTable(); |
| setLeadFromTable(); |
| // setAnchor(table.getSelectedRow()); |
| // setLead(table.getSelectedRow()); |
| } |
| int newLead = getRowLead() + 1; |
| int newAnchor = getRowAnchor(); |
| if (newLead > numUsableRows) { |
| scrollBar.scrollDownOrRight(); |
| --newLead; |
| --newAnchor; |
| } |
| setSelection(newAnchor, newLead, getColAnchor(), getColLead()); |
| // printSelection(); |
| endUpdate(); |
| } |
| }); |
| // Shift + Right arrow |
| installActionWrapper(map, "selectNextColumnExtendSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| if (!haveAnchor()) { |
| setAnchorFromTable(); |
| setLeadFromTable(); |
| } |
| int newLead = Math.min(model.getColumnCount() - 1, getColLead() + 1); |
| setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead); |
| // printSelection(); |
| endUpdate(); |
| } |
| }); |
| // Shift + Page up |
| installActionWrapper(map, "scrollUpExtendSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| if (!haveAnchor()) { |
| setAnchorFromTable(); |
| setLeadFromTable(); |
| // setAnchor(table.getSelectedRow()); |
| // setLead(table.getSelectedRow()); |
| } |
| int newLead = getRowLead() - numUsableRows; |
| int newAnchor = getRowAnchor(); |
| if (newLead < 0) { |
| scrollBar.pageUpOrLeft(); |
| newLead += numUsableRows; |
| newAnchor += numUsableRows; |
| } |
| setSelection(newAnchor, newLead, getColAnchor(), getColLead()); |
| // printSelection(); |
| endUpdate(); |
| } |
| }); |
| // Shift + Page down |
| installActionWrapper(map, "scrollDownExtendSelection", new ActionWrapper() { |
| public void actionPerformed(ActionEvent e) { |
| beginUpdate(); |
| if (!haveAnchor()) { |
| setAnchorFromTable(); |
| setLeadFromTable(); |
| // setAnchor(table.getSelectedRow()); |
| // setLead(table.getSelectedRow()); |
| } |
| int newLead = getRowLead() + numUsableRows; |
| int newAnchor = getRowAnchor(); |
| if (newLead > numUsableRows) { |
| scrollBar.pageDownOrRight(); |
| newLead -= numUsableRows; |
| newAnchor -= numUsableRows; |
| } |
| setSelection(newAnchor, newLead, getColAnchor(), getColLead()); |
| // printSelection(); |
| endUpdate(); |
| } |
| }); |
| |
| // Clear our notion of selection upon mouse press |
| table.addMouseListener(new MouseAdapter() { |
| public void mousePressed(MouseEvent e) { |
| if (shouldIgnore(e)) { |
| return; |
| } |
| // Make shift-clicking work properly |
| if (e.isShiftDown()) { |
| maybeGrabSelection(); |
| return; |
| } |
| // System.err.println(" Clearing selection on mouse press"); |
| clearSelection(); |
| } |
| }); |
| |
| // Watch for mouse going out of bounds |
| table.addMouseMotionListener(new MouseMotionAdapter() { |
| public void mouseDragged(MouseEvent e) { |
| if (shouldIgnore(e)) { |
| // System.err.println(" (Ignoring consumed mouse event)"); |
| return; |
| } |
| |
| // Look for drag events outside table and scroll if necessary |
| Point p = e.getPoint(); |
| if (table.rowAtPoint(p) == -1) { |
| // See whether we are above or below the table |
| Rectangle rect = new Rectangle(); |
| getBounds(rect); |
| beginUpdate(); |
| if (p.y < rect.y) { |
| // System.err.println(" Scrolling up due to mouse event"); |
| // Scroll up |
| scrollBar.scrollUpOrLeft(); |
| setSelection(getRowAnchor(), 0, getColAnchor(), getColLead()); |
| } else { |
| // System.err.println(" Scrolling down due to mouse event"); |
| // Scroll down |
| scrollBar.scrollDownOrRight(); |
| setSelection(getRowAnchor(), numUsableRows, getColAnchor(), getColLead()); |
| } |
| // printSelection(); |
| endUpdate(); |
| } else { |
| maybeGrabSelection(); |
| } |
| } |
| }); |
| |
| |
| add(table, BorderLayout.CENTER); |
| |
| // Make sure we recompute number of visible rows |
| addComponentListener(new ComponentAdapter() { |
| public void componentResized(ComponentEvent e) { |
| recomputeNumVisibleRows(); |
| constrain(); |
| } |
| }); |
| addHierarchyListener(new HierarchyListener() { |
| public void hierarchyChanged(HierarchyEvent e) { |
| recomputeNumVisibleRows(); |
| constrain(); |
| } |
| }); |
| updateFromScrollBar(); |
| } |
| |
| /** Makes the given address visible somewhere in the window */ |
| public void makeVisible(Address addr) { |
| BigInteger bi = addressToBigInt(addr); |
| scrollBar.setValueHP(bi); |
| } |
| |
| //---------------------------------------------------------------------- |
| // Internals only below this point |
| // |
| |
| private void setupScrollBar() { |
| if (is64Bit) { |
| // 64-bit mode |
| scrollBar = |
| new HighPrecisionJScrollBar( |
| Scrollbar.VERTICAL, |
| new BigInteger(1, new byte[] { |
| (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}), |
| new BigInteger(1, new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}), |
| new BigInteger(1, new byte[] { |
| (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, |
| (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC})); |
| scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08})); |
| scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40})); |
| } else { |
| // 32-bit mode |
| scrollBar= |
| new HighPrecisionJScrollBar( |
| Scrollbar.VERTICAL, |
| new BigInteger(1, new byte[] { |
| (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00}), |
| new BigInteger(1, new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}), |
| new BigInteger(1, new byte[] { |
| (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC})); |
| scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04})); |
| scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20})); |
| } |
| scrollBar.addChangeListener(new ChangeListener() { |
| public void stateChanged(ChangeEvent e) { |
| updateFromScrollBar(); |
| } |
| }); |
| } |
| |
| private void updateFromScrollBar() { |
| beginUpdate(); |
| BigInteger oldStartVal = startVal; |
| startVal = scrollBar.getValueHP(); |
| constrain(); |
| model.fireTableDataChanged(); |
| if (oldStartVal != null) { |
| modifySelection(oldStartVal.subtract(startVal).intValue() / addressSize); |
| } |
| endUpdate(); |
| } |
| |
| private void constrain() { |
| BigInteger offset = new BigInteger(Integer.toString(addressSize * (numUsableRows))); |
| BigInteger endVal = startVal.add(offset); |
| if (endVal.compareTo(scrollBar.getMaximumHP()) > 0) { |
| startVal = scrollBar.getMaximumHP().subtract(offset); |
| endVal = scrollBar.getMaximumHP(); |
| scrollBar.setValueHP(startVal); |
| model.fireTableDataChanged(); |
| } |
| } |
| |
| private void recomputeNumVisibleRows() { |
| Rectangle rect = new Rectangle(); |
| getBounds(rect); |
| int h = table.getRowHeight(); |
| numVisibleRows = (rect.height + (h - 1)) / h; |
| numUsableRows = numVisibleRows - 2; |
| scrollBar.setBlockIncrementHP(new BigInteger(Integer.toString(addressSize * (numUsableRows)))); |
| model.fireTableDataChanged(); |
| // FIXME: refresh selection |
| } |
| |
| private String bigIntToHexString(BigInteger bi) { |
| StringBuffer buf = new StringBuffer(); |
| buf.append("0x"); |
| String val = bi.toString(16); |
| for (int i = 0; i < ((2 * addressSize) - val.length()); i++) { |
| buf.append('0'); |
| } |
| buf.append(val); |
| return buf.toString(); |
| } |
| |
| private Address bigIntToAddress(BigInteger i) { |
| String s = bigIntToHexString(i); |
| return debugger.parseAddress(s); |
| } |
| |
| private BigInteger addressToBigInt(Address a) { |
| String s = addressToString(a); |
| if (!s.startsWith("0x")) { |
| throw new NumberFormatException(s); |
| } |
| return new BigInteger(s.substring(2), 16); |
| } |
| |
| private String addressToString(Address a) { |
| if (a == null) { |
| if (is64Bit) { |
| return "0x0000000000000000"; |
| } else { |
| return "0x00000000"; |
| } |
| } |
| return a.toString(); |
| } |
| |
| private static void installActionWrapper(ActionMap map, |
| String actionName, |
| ActionWrapper wrapper) { |
| wrapper.setParent(map.get(actionName)); |
| map.put(actionName, wrapper); |
| } |
| |
| private boolean shouldIgnore(MouseEvent e) { |
| return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled())); |
| } |
| |
| private void clearSelection() { |
| haveAnchor = false; |
| haveLead = false; |
| } |
| |
| private int updateLevel; |
| private boolean updating() { return updateLevel > 0; } |
| private void beginUpdate() { ++updateLevel; } |
| private void endUpdate() { --updateLevel; } |
| |
| private boolean haveAnchor() { return haveAnchor; } |
| private boolean haveLead() { return haveLead; } |
| private boolean haveSelection() { return haveAnchor() && haveLead(); } |
| private int getRowAnchor() { return rowAnchorIndex; } |
| private int getColAnchor() { return colAnchorIndex; } |
| private int getRowLead() { return rowLeadIndex; } |
| private int getColLead() { return colLeadIndex; } |
| |
| private void setAnchorFromTable() { |
| setAnchor(table.getSelectionModel().getAnchorSelectionIndex(), |
| table.getColumnModel().getSelectionModel().getAnchorSelectionIndex()); |
| } |
| private void setLeadFromTable() { |
| setLead(table.getSelectionModel().getAnchorSelectionIndex(), |
| table.getColumnModel().getSelectionModel().getAnchorSelectionIndex()); |
| } |
| private void setAnchor(int row, int col) { |
| rowAnchorIndex = row; |
| colAnchorIndex = col; |
| haveAnchor = true; |
| } |
| private void setLead(int row, int col) { |
| rowLeadIndex = row; |
| colLeadIndex = col; |
| haveLead = true; |
| } |
| private int clamp(int val, int min, int max) { |
| return Math.max(Math.min(val, max), min); |
| } |
| private void maybeGrabSelection() { |
| if (table.getSelectedRow() != -1) { |
| // Grab selection |
| ListSelectionModel rowSel = table.getSelectionModel(); |
| ListSelectionModel colSel = table.getColumnModel().getSelectionModel(); |
| if (!haveAnchor()) { |
| // System.err.println("Updating from table's selection"); |
| setSelection(rowSel.getAnchorSelectionIndex(), rowSel.getLeadSelectionIndex(), |
| colSel.getAnchorSelectionIndex(), colSel.getLeadSelectionIndex()); |
| } else { |
| // System.err.println("Updating lead from table's selection"); |
| setSelection(getRowAnchor(), rowSel.getLeadSelectionIndex(), |
| getColAnchor(), colSel.getLeadSelectionIndex()); |
| } |
| // printSelection(); |
| } |
| } |
| private void setSelection(int rowAnchor, int rowLead, int colAnchor, int colLead) { |
| setAnchor(rowAnchor, colAnchor); |
| setLead(rowLead, colLead); |
| table.setRowSelectionInterval(clamp(rowAnchor, 0, numUsableRows), |
| clamp(rowLead, 0, numUsableRows)); |
| table.setColumnSelectionInterval(colAnchor, colLead); |
| } |
| private void modifySelection(int amount) { |
| if (haveSelection()) { |
| setSelection(getRowAnchor() + amount, getRowLead() + amount, |
| getColAnchor(), getColLead()); |
| } |
| } |
| private void printSelection() { |
| System.err.println("Selection updated to (" + |
| model.getValueAt(getRowAnchor(), getColAnchor()) + |
| ", " + |
| model.getValueAt(getRowLead(), getColLead()) + ") [(" + |
| getRowAnchor() + ", " + getColAnchor() + "), (" + |
| getRowLead() + ", " + getColLead() + ")]"); |
| } |
| } |