/*******************************************************************************
 * Copyright (c) 2006, 2010, 2012 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Ted R Williams (Wind River Systems, Inc.) - initial implementation
 *     Randy Rohrbach (Wind River Systems, Inc.) - Copied and modified to create the floating point plugin
 *******************************************************************************/

package org.eclipse.cdt.debug.ui.memory.floatingpoint;

import java.math.BigInteger;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Caret;

public abstract class FPAbstractPane extends Canvas
{
    protected Rendering fRendering;

    // Selection state

    protected boolean fSelectionStarted = false;
    protected boolean fSelectionInProgress = false;
    protected BigInteger fSelectionStartAddress = null;
    protected int fSelectionStartAddressSubPosition;

    // Caret

    protected Caret fCaret = null;

    // Character may not fall on byte boundary

    protected int fSubCellCaretPosition = 0;
    protected int fOldSubCellCaretPosition = 0;
    protected boolean fCaretEnabled = false;
    protected BigInteger fCaretAddress = null;

    // Storage

    protected int fRowCount = 0;
    protected boolean fPaneVisible = true;

    // Mouse listener class

    class AbstractPaneMouseListener implements MouseListener
    {
        @Override
        public void mouseUp(MouseEvent me)
        {
            // Move the caret

            positionCaret(me.x, me.y);

            fCaret.setVisible(true);

            if (fSelectionInProgress && me.button == 1)
                endSelection(me.x, me.y);

            fSelectionInProgress = fSelectionStarted = false;
        }

        // Mouse down click

        @Override
        public void mouseDown(MouseEvent me)
        {
            // Any click, whether inside this cell or elsewhere, terminates the edit and acts the same as a carriage return would.

            handleCarriageReturn();

            // Switch focus and check for selection

            FPAbstractPane.this.forceFocus();
            positionCaret(me.x, me.y);
            fCaret.setVisible(false);

            if (me.button == 1)
            {
                // If shift is down and we have an existing start address, append selection

                if ((me.stateMask & SWT.SHIFT) != 0 && fRendering.getSelection().getStart() != null)
                {
                    // If the pane doesn't have a selection start (the selection was created in a different
                    // pane) then initialize the pane's selection start to the rendering's selection start.

                    if (FPAbstractPane.this.fSelectionStartAddress == null)
                        FPAbstractPane.this.fSelectionStartAddress = fRendering.getSelection().getStart();

                    FPAbstractPane.this.fSelectionStarted = true;
                    FPAbstractPane.this.appendSelection(me.x, me.y);

                }
                else
                {
                    // Start a new selection

                    FPAbstractPane.this.startSelection(me.x, me.y);
                }
            }

        }

        // Double click

        @Override
        public void mouseDoubleClick(MouseEvent me)
        {
            handleMouseDoubleClick(me);
        }
    }

    // Mouse move listener class

    class AbstractPaneMouseMoveListener implements MouseMoveListener
    {
        @Override
        public void mouseMove(MouseEvent me)
        {
            if (fSelectionStarted)
            {
                fSelectionInProgress = true;
                appendSelection(me.x, me.y);
            }
        }
    }

    // Focus listener class

    class AbstractPaneFocusListener implements FocusListener
    {
        @Override
        public void focusLost(FocusEvent fe)
        {
            IPreferenceStore store = FPRenderingPlugin.getDefault().getPreferenceStore();

            if (FPRenderingPreferenceConstants.MEM_EDIT_BUFFER_SAVE_ON_ENTER_ONLY.equals(store.getString(FPRenderingPreferenceConstants.MEM_EDIT_BUFFER_SAVE)))
                fRendering.getViewportCache().clearEditBuffer();
            else
                fRendering.getViewportCache().writeEditBuffer();

            // clear the pane local selection start
            FPAbstractPane.this.fSelectionStartAddress = null;
        }

        @Override
        public void focusGained(FocusEvent fe)
        {
            // Set the floating point edit mode indicator if the user clicked in the Data Pane; otherwise clear it

            if (FPAbstractPane.this instanceof FPDataPane)
                fRendering.displayEditModeIndicator(true);
            else
                fRendering.displayEditModeIndicator(false);
        }
    }

    // Key listener class

    class AbstractPaneKeyListener implements KeyListener
    {
        @Override
        public void keyPressed(KeyEvent ke)
        {
            fOldSubCellCaretPosition = fSubCellCaretPosition;

            // Shift

            if ((ke.stateMask & SWT.SHIFT) != 0)
            {
                switch (ke.keyCode)
                {
                    case SWT.ARROW_RIGHT:
                    case SWT.ARROW_LEFT:
                    case SWT.ARROW_UP:
                    case SWT.ARROW_DOWN:
                    case SWT.PAGE_DOWN:
                    case SWT.PAGE_UP:
                    {
                        if (fRendering.getSelection().getStart() == null)
                            fRendering.getSelection().setStart(fCaretAddress.add(BigInteger.valueOf(fRendering.getAddressesPerColumn())), fCaretAddress);
                        break;
                    }
                }
            }

            // Arrow, Page, Insert, Escape and standard characters

            if (ke.keyCode == SWT.ARROW_RIGHT)
            {
                handleRightArrowKey();
            }
            else if (ke.keyCode == SWT.ARROW_LEFT || ke.keyCode == SWT.BS)
            {
                handleLeftArrowKey();
            }
            else if (ke.keyCode == SWT.ARROW_DOWN)
            {
                handleDownArrowKey();
            }
            else if (ke.keyCode == SWT.ARROW_UP)
            {
                handleUpArrowKey();
            }
            else if (ke.keyCode == SWT.PAGE_DOWN)
            {
                handlePageDownKey();
            }
            else if (ke.keyCode == SWT.PAGE_UP)
            {
                handlePageUpKey();
            }
            else if (ke.keyCode == SWT.INSERT)
            {
                handleInsertKey();
            }
            else if (ke.keyCode == SWT.ESC)
            {
                fRendering.getViewportCache().clearEditBuffer();
                handleCTRLZ();
            }
            else if (ke.character == '\r')
            {
                fRendering.getViewportCache().writeEditBuffer();
                handleCarriageReturn();
            }
            else if (FPutilities.validEditCharacter(ke.character))
            {
                // Check for selection

                if (fRendering.getSelection().hasSelection())
                {
                    setCaretAddress(fRendering.getSelection().getLow());
                    fSubCellCaretPosition = 0;
                }

                // Add the chatacter to the cell

                editCell(fCaretAddress, fSubCellCaretPosition, ke.character);
            }

            // Control

            if ((ke.stateMask & SWT.CTRL) != 0)
            {
                // CTRL/Z

                if (ke.keyCode == 'z' || ke.keyCode == 'Z')
                    handleCTRLZ();
            }

            // Alt

            if ((ke.stateMask & SWT.ALT) != 0)
            {
                // Future use
            }

            // Shift

            if ((ke.stateMask & SWT.SHIFT) != 0)
            {
                switch (ke.keyCode)
                {
                    case SWT.ARROW_RIGHT:
                    case SWT.ARROW_LEFT:
                    case SWT.ARROW_UP:
                    case SWT.ARROW_DOWN:
                    case SWT.PAGE_DOWN:
                    case SWT.PAGE_UP:
                        fRendering.getSelection().setEnd(fCaretAddress.add(BigInteger.valueOf(fRendering.getAddressesPerColumn())), fCaretAddress);
                        break;
                }
            }
            else if (ke.keyCode != SWT.SHIFT)
            {
                // If it's a SHIFT key, keep the selection since we may add to it
                fRendering.getSelection().clear();
            }
        }

        @Override
        public void keyReleased(KeyEvent ke)
        {
            // do nothing
        }
    }

    class AbstractPanePaintListener implements PaintListener
    {
        @Override
        public void paintControl(PaintEvent pe)
        {
            FPAbstractPane.this.paint(pe);
        }
    }

    public FPAbstractPane(Rendering rendering)
    {
        super(rendering, SWT.DOUBLE_BUFFERED);

        fRendering = rendering;

        try
        {
            fCaretAddress = rendering.getBigBaseAddress();
        }
        catch (Exception e)
        {
            // do nothing
        }

        // pref

        this.setFont(fRendering.getFont());

        GC gc = new GC(this);
        gc.setFont(this.getFont());
        fCaret = new Caret(this, SWT.NONE);
        fCaret.setSize(1, gc.stringExtent("|").y); //$NON-NLS-1$
        gc.dispose();

        this.addPaintListener(createPaintListener());
        this.addMouseListener(createMouseListener());
        this.addMouseMoveListener(createMouseMoveListener());
        this.addKeyListener(createKeyListener());
        this.addFocusListener(createFocusListener());
    }

    // Listener methods

    protected MouseListener createMouseListener()
    {
        return new AbstractPaneMouseListener();
    }

    protected MouseMoveListener createMouseMoveListener()
    {
        return new AbstractPaneMouseMoveListener();
    }

    protected FocusListener createFocusListener()
    {
        return new AbstractPaneFocusListener();
    }

    protected KeyListener createKeyListener()
    {
        return new AbstractPaneKeyListener();
    }

    protected PaintListener createPaintListener()
    {
        return new AbstractPanePaintListener();
    }

    // Right arrow

    protected void handleRightArrowKey()
    {
        fSubCellCaretPosition++;

        if (fSubCellCaretPosition >= getCellCharacterCount())
        {
            // We've moved beyond the end of the cell: End the edit to the previous cell.

            handleCarriageReturn();

            // Move to the next cell; ensure that caret is within the addressable range

            fSubCellCaretPosition = 0;
            BigInteger newCaretAddress = fCaretAddress.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength()));

            if (newCaretAddress.compareTo(fRendering.getMemoryBlockEndAddress()) > 0)
                fSubCellCaretPosition = getCellCharacterCount();
            else
                setCaretAddress(newCaretAddress);
        }

        updateTheCaret();
        ensureCaretWithinViewport();
    }

    // Left arrow

    protected void handleLeftArrowKey()
    {
        fSubCellCaretPosition--;

        if (fSubCellCaretPosition < 0)
        {
            // We've moved beyond the beginning of the cell:  This action ends the edit to the previous cell.

            handleCarriageReturn();

            // Move to the previous cell; ensure that caret is within the addressable range

            fSubCellCaretPosition = getCellCharacterCount() - 1;
            BigInteger newCaretAddress = fCaretAddress.subtract(BigInteger.valueOf(fRendering.getFPDataType().getByteLength()));

            if (newCaretAddress.compareTo(fRendering.getMemoryBlockStartAddress()) < 0)
                fSubCellCaretPosition = 0;
            else
                setCaretAddress(newCaretAddress);
        }

        updateTheCaret();
        ensureCaretWithinViewport();
    }

    // Down arrow

    protected void handleDownArrowKey()
    {
        // We've moved beyond the beginning of the cell:  This action ends the edit to the previous cell.

        handleCarriageReturn();

        // Ensure that caret is within the addressable range

        BigInteger newCaretAddress = fCaretAddress.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength() * fRendering.getColumnCount()));
        setCaretAddress(newCaretAddress);
        updateTheCaret();
        ensureCaretWithinViewport();
    }

    // Up arrow

    protected void handleUpArrowKey()
    {
        // We've moved beyond the beginning of the cell:  This action ends the edit to the previous cell.

        handleCarriageReturn();

        // Ensure that caret is within the addressable range

        BigInteger newCaretAddress = fCaretAddress.subtract(BigInteger.valueOf(fRendering.getFPDataType().getByteLength() * fRendering.getColumnCount()));
        setCaretAddress(newCaretAddress);
        updateTheCaret();
        ensureCaretWithinViewport();
    }

    // Page down

    protected void handlePageDownKey()
    {
        // We've moved beyond the beginning of the cell:  This action ends the edit to the previous cell.

        handleCarriageReturn();

        // Ensure that caret is within the addressable range

        BigInteger newCaretAddress = fCaretAddress.add(BigInteger.valueOf(fRendering.getAddressableCellsPerRow() * (fRendering.getRowCount() - 1)));
        setCaretAddress(newCaretAddress);
        updateTheCaret();
        ensureCaretWithinViewport();
    }

    // Page up

    protected void handlePageUpKey()
    {
        // We've moved beyond the beginning of the cell:  This action ends the edit to the previous cell.

        handleCarriageReturn();

        // Ensure that caret is within the addressable range

        BigInteger newCaretAddress = fCaretAddress.subtract(BigInteger.valueOf(fRendering.getAddressableCellsPerRow() * (fRendering.getRowCount() - 1)));
        setCaretAddress(newCaretAddress);
        updateTheCaret();
        ensureCaretWithinViewport();
    }

    // Insert key

    protected void handleInsertKey()
    {
        // If focus is in the Data Pane, toggle Insert/Overwrite mode and make sure the cell edit
        // status line indicator is displayed.  Otherwise, make clear the status line indicator.

        if (FPAbstractPane.this instanceof FPDataPane)
        {
            if (!fRendering.isEditingCell()) fRendering.setInsertMode(!fRendering.insertMode());
            fRendering.displayEditModeIndicator(true);
        }
        else
            fRendering.displayEditModeIndicator(false);
    }

    // Double-click

    protected void handleMouseDoubleClick(MouseEvent me)
    {
        try
        {
            BigInteger address = getViewportAddress(me.x / getCellWidth(), me.y / getCellHeight());

            fRendering.getSelection().clear();
            fRendering.getSelection().setStart(address.add(BigInteger.valueOf(fRendering.getAddressesPerColumn())), address);
            fRendering.getSelection().setEnd(address.add(BigInteger.valueOf(fRendering.getAddressesPerColumn())), address);
        }
        catch (DebugException de)
        {
            // do nothing
        }
    }

    // Carriage return

    protected void handleCarriageReturn()
    {
        // If we're not editing a cell or there is no string buffer to use, nothing to do:  Exit edit mode and return.

        if (!fRendering.isEditingCell() || fRendering.getEditBuffer() == null)
        {
            fRendering.endCellEditing();
            return;
        }

        // Remove all whitespace from the string buffer.

        fRendering.setEditBuffer(new StringBuffer(fRendering.getEditBuffer().toString().trim().replaceAll(" ", ""))); //$NON-NLS-1$ //$NON-NLS-2$

        // Check the string to make sure it's in valid, acceptable form.

        if (FPutilities.isValidFormat(fRendering.getEditBuffer().toString()))
        {
            // Valid string:  Convert it to a byte array and write the buffer back to memory;
            // a subsequent re-draw/paint converts it to normalized scientific notation.

            fRendering.convertAndUpdateCell(fRendering.getCellEditAddress(), fRendering.getEditBuffer().toString());
        }
        else
        {
            // Invalid string: Create the error text and restore the previous value

            String errorText = NLS.bind(FPRenderingMessages.getString("FPRendering.ERROR_FPENTRY_POPUP_TEXT"), fRendering.getEditBuffer().toString()); //$NON-NLS-1$

            try
            {
                fRendering.setEditBuffer(new StringBuffer(fRendering.fDataPane.bytesToSciNotation(fRendering.getBytes(fCaretAddress, fRendering.getFPDataType().getByteLength()))));
            }
            catch (DebugException e)
            {
                e.printStackTrace();
            }

            // Put together the pop-up window components and show the user the error

            String statusString = FPRenderingMessages.getString("FPRendering.ERROR_FPENTRY_STATUS"); //$NON-NLS-1$
            Status status = new Status(IStatus.ERROR, FPRenderingPlugin.getUniqueIdentifier(), statusString);
            FPutilities.popupMessage(FPRenderingMessages.getString("FPRendering.ERROR_FPENTRY_POPUP_TITLE"), errorText, status); //$NON-NLS-1$
        }

        // Exit cell-edit mode

        fRendering.endCellEditing();
    }

    // CTRL/Z handling

    protected void handleCTRLZ()
    {
        // CTRL/Z:  Replace the cell contents with the original value and exit "number edit mode"

        try
        {
            fRendering.setEditBuffer(new StringBuffer(fRendering.fDataPane.bytesToSciNotation(fRendering.getBytes(fCaretAddress, fRendering.getFPDataType().getByteLength()))));
        }
        catch (DebugException e)
        {
            e.printStackTrace();
        }

        fRendering.endCellEditing();
    }

    // Other getter/setters

    protected boolean isPaneVisible()
    {
        return fPaneVisible;
    }

    protected void setPaneVisible(boolean visible)
    {
        fPaneVisible = visible;
        this.setVisible(visible);
    }

    protected int getNumberOfBytesRepresentedByColumn()
    {
        return fRendering.getCharsPerColumn();
    }

    protected void editCell(BigInteger cellAddress, int subCellPosition, char character)
    {
        // Do nothing; overridden in subclass FPDataPane
    }

    // Set the caret address

    protected void setCaretAddress(BigInteger caretAddress)
    {
        // Ensure that caret is within the addressable range

        if ((caretAddress.compareTo(fRendering.getMemoryBlockStartAddress()) >= 0) && (caretAddress.compareTo(fRendering.getMemoryBlockEndAddress()) <= 0))
        {
            fCaretAddress = caretAddress;
        }
        else if (caretAddress.compareTo(fRendering.getMemoryBlockStartAddress()) < 0)
        {
            // Calculate offset from the beginning of the row

            int cellOffset = fCaretAddress.subtract(fRendering.getViewportStartAddress()).intValue();
            int row = cellOffset / (fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter());

            cellOffset -= row * fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter();

            fCaretAddress = fRendering.getMemoryBlockStartAddress().add(BigInteger.valueOf(cellOffset / fRendering.getAddressableSize()));
        }
        else if (caretAddress.compareTo(fRendering.getMemoryBlockEndAddress()) > 0)
        {
            // Calculate offset from the end of the row

            int cellOffset = fCaretAddress.subtract(fRendering.getViewportEndAddress()).intValue() + 1;
            int row = cellOffset / (fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter());

            cellOffset -= row * fRendering.getBytesPerRow() / fRendering.getBytesPerCharacter();

            fCaretAddress = fRendering.getMemoryBlockEndAddress().add(BigInteger.valueOf(cellOffset / fRendering.getAddressableSize()));
        }

        fRendering.setCaretAddress(fCaretAddress);
    }

    protected boolean isOdd(int value)
    {
        return (value / 2) * 2 == value;
    }

    protected void updateTheCaret()
    {
        try
        {
            if (fCaretAddress != null)
            {
                Point cellPosition = getCellLocation(fCaretAddress);

                if (cellPosition != null)
                    fCaret.setLocation(cellPosition.x + fSubCellCaretPosition * getCellCharacterWidth(), cellPosition.y);
            }
        }
        catch (Exception e)
        {
            fRendering.logError(FPRenderingMessages.getString("FPRendering.FAILURE_POSITION_CURSOR"), e); //$NON-NLS-1$
        }
    }

    // This method scrolls the viewport to insure that the caret is within the viewable area

    protected void ensureCaretWithinViewport() // TODO getAddressableSize() > 1 ?
    {
        // If the caret is before the viewport start if so, scroll viewport up by several rows

        BigInteger rowCount      = BigInteger.valueOf(getRowCount());
        BigInteger rowMemBytes   = BigInteger.valueOf(fRendering.getFPDataType().getByteLength() * fRendering.getColumnCount());
        BigInteger viewableBytes = rowCount.multiply(rowMemBytes);
        BigInteger viewableEnd   = fRendering.getViewportStartAddress().add(viewableBytes);

        if (fCaretAddress.compareTo(fRendering.getViewportStartAddress()) < 0)
        {
            fRendering.setViewportStartAddress(fRendering.getViewportStartAddress().subtract(rowMemBytes));
            fRendering.ensureViewportAddressDisplayable();
            fRendering.gotoAddress(fRendering.getViewportStartAddress());
        }

        // If the caret is after the viewport end if so, scroll viewport down by appropriate rows

        else if (fCaretAddress.compareTo(viewableEnd) >= 0)
        {
            fRendering.setViewportStartAddress(fRendering.getViewportStartAddress().add(rowMemBytes));
            fRendering.ensureViewportAddressDisplayable();
            fRendering.gotoAddress(fRendering.getViewportStartAddress());
        }

        fRendering.setCaretAddress(fCaretAddress);
    }

    protected void advanceCursor()
    {
        handleRightArrowKey();
    }

    protected void positionCaret(int x, int y)
    {
        // do nothing
    }

    protected int getRowCount()
    {
        return fRowCount;
    }

    protected void setRowCount()
    {
        fRowCount = getBounds().height / getCellHeight();
    }

    protected void settingsChanged()
    {
        fSubCellCaretPosition = 0;
    }

    // Start selection

    protected void startSelection(int x, int y)
    {
        try
        {
            BigInteger address = getViewportAddress(x / getCellWidth(), y / getCellHeight());

            if (address != null)
            {
                this.fSelectionStartAddress = address;
                Point cellPosition = getCellLocation(address);

                if (cellPosition != null)
                {
                    int offset = x - cellPosition.x;
                    fSelectionStartAddressSubPosition = offset / getCellCharacterWidth();
                }

                fRendering.getSelection().clear();
                fRendering.getSelection().setStart(address.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength())), address);
                fSelectionStarted = true;

                new CopyAction(fRendering, DND.SELECTION_CLIPBOARD).run();
            }
        }
        catch (DebugException e)
        {
            fRendering.logError(FPRenderingMessages.getString("FPRendering.FAILURE_START_SELECTION"), e); //$NON-NLS-1$
        }
    }

    // End selection

    protected void endSelection(int x, int y)
    {
        appendSelection(x, y);
        fSelectionInProgress = false;
    }

    protected void appendSelection(int x, int y)
    {
        try
        {
            if (this.fSelectionStartAddress == null) return;

            BigInteger address = getViewportAddress(x / getCellWidth(), y / getCellHeight());

            if (address.compareTo(this.fSelectionStartAddress) == 0)
            {
                // Sub-cell selection

                Point cellPosition = getCellLocation(address);
                int offset = x - cellPosition.x;
                int subCellCharacterPosition = offset / getCellCharacterWidth();

                if (Math.abs(subCellCharacterPosition - this.fSelectionStartAddressSubPosition) > this.getCellCharacterCount() / 4)
                {
                    fRendering.getSelection().setEnd(address.add(BigInteger.valueOf((fRendering.getFPDataType().getByteLength()))), address);
                }
                else
                {
                    fRendering.getSelection().setEnd(null, null);
                }
            }
            else
            {
                fRendering.getSelection().setEnd(address.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength())), address);
            }

            if (fRendering.getSelection().getEnd() != null)
            {
                this.fCaretAddress = fRendering.getSelection().getEnd();
                this.fSubCellCaretPosition = 0;
            }

            updateTheCaret();

            new CopyAction(fRendering, DND.SELECTION_CLIPBOARD).run();
        }
        catch (Exception e)
        {
            fRendering.logError(FPRenderingMessages.getString("FPRendering.FAILURE_APPEND_SELECTION"), e); //$NON-NLS-1$
        }
    }

    protected void paint(PaintEvent pe)
    {
        fRowCount = getBounds().height / getCellHeight();

        if (fRendering.isDirty())
        {
            fRendering.setDirty(false);
            fRendering.refresh();
        }
    }

    abstract protected BigInteger getViewportAddress(int col, int row) throws DebugException;

    protected Point getCellLocation(BigInteger address)
    {
        return null;
    }

    protected String getCellText(MemoryByte bytes[])
    {
        return null;
    }

    abstract protected int getCellWidth();

    abstract protected int getCellCharacterCount();

    @Override
    public void setFont(Font font)
    {
        super.setFont(font);
        fCharacterWidth = -1;
        fCellHeight = -1;
        fTextHeight = -1;
    }

    private int fCellHeight = -1; // called often, cache

    protected int getCellHeight()
    {
        if (fCellHeight == -1)
        {
            fCellHeight = getCellTextHeight() + (fRendering.getCellPadding() * 2);
        }

        return fCellHeight;
    }

    private int fCharacterWidth = -1; // called often, cache

    protected int getCellCharacterWidth()
    {
        if (fCharacterWidth == -1)
        {
            GC gc = new GC(this);
            gc.setFont(fRendering.getFont());
            fCharacterWidth = gc.getAdvanceWidth('F');
            gc.dispose();
        }

        return fCharacterWidth;
    }

    private int fTextHeight = -1; // called often, cache

    protected int getCellTextHeight()
    {
        if (fTextHeight == -1)
        {
            GC gc = new GC(this);
            gc.setFont(fRendering.getFont());
            FontMetrics fontMetrics = gc.getFontMetrics();
            fTextHeight = fontMetrics.getHeight();
            gc.dispose();
        }
        return fTextHeight;
    }
}
