org.eclipse.cdt.debug.ui.memory.floatingpoint.Rendering.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.cdt.debug.ui.memory.floatingpoint.Rendering.java

Source

/*******************************************************************************
 * 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.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import org.eclipse.cdt.debug.ui.memory.floatingpoint.FPutilities.FPDataType;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil;
import org.eclipse.debug.internal.ui.views.memory.renderings.GoToAddressComposite;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.UIJob;

@SuppressWarnings("restriction")
public class Rendering extends Composite implements IDebugEventSetListener {
    // The IMemoryRendering parent

    private FPRendering fParent;

    // Controls

    protected FPAddressPane fAddressPane;
    protected FPDataPane fDataPane;
    private GoToAddressComposite fAddressBar;
    protected Control fAddressBarControl;
    private Selection fSelection = new Selection();

    // Internal management

    BigInteger fViewportAddress = null; // Default visibility for performance
    BigInteger fMemoryBlockStartAddress = null; // Starting address
    BigInteger fMemoryBlockEndAddress = null; // Ending address
    protected BigInteger fBaseAddress = null; // Base address
    protected int fColumnCount = 0; // Auto calculate can be disabled by user, making this user settable
    protected int fBytesPerRow = 0; // Number of bytes per row are displayed
    int fScrollSelection = 0; // Scroll selection
    private BigInteger fCaretAddress = null; // Caret/cursor position
    private boolean fCellEditState = false; // Cell editing mode:  'true' = currently editing a cell; 'false' = not editing a cell
    private BigInteger cellEditAddress = null; // The address of the cell currently being edited
    private BigInteger memoryAddress = null; // The memory address associated with the cell that currently being edited
    private StringBuffer fEditBuffer = null; // Character buffer used during editing
    static boolean initialDisplayModeSet = false; // Initial display mode been set to the same endianness as the target

    // Constants used to identify the panes

    public final static int PANE_ADDRESS = 1;
    public final static int PANE_DATA = 2;

    // Decimal precision used when converting between scroll units and number of memory
    // rows. Calculations do not need to be exact; two decimal places is good enough.

    static private final MathContext SCROLL_CONVERSION_PRECISION = new MathContext(2);

    // Constants used to identify text, maybe java should be queried for all available sets

    public final static int TEXT_ISO_8859_1 = 1;
    public final static int TEXT_USASCII = 2;
    public final static int TEXT_UTF8 = 3;
    protected final static int TEXT_UTF16 = 4;

    // Internal constants

    public final static int COLUMNS_AUTO_SIZE_TO_FIT = 0;

    // View internal settings

    private int fCellPadding = 2;
    private int fPaneSpacing = 16;
    private String fPaddingString = " "; //$NON-NLS-1$

    // Flag whether the memory cache is dirty

    private boolean fCacheDirty = false;

    // Update modes

    public final static int UPDATE_ALWAYS = 1;
    public final static int UPDATE_ON_BREAKPOINT = 2;
    public final static int UPDATE_MANUAL = 3;
    public int fUpdateMode = UPDATE_ALWAYS;

    // Constants for cell-width calculations

    private static final int DECIMAL_POINT_SIZE = 1;
    private static final int SIGN_SIZE = 1;
    private static final int EXPONENT_CHARACTER_SIZE = 1;
    private static final int EXPONENT_VALUE_SIZE = 3;

    // User settings

    private FPDataType fFPDataType = FPDataType.FLOAT; // Default to float data type
    private int fDisplayedPrecision = 8; // The default number of digits of displayed precision
    private int fCharsPerColumn = charsPerColumn(); // Figure out the initial cell-width size
    private int fColumnsSetting = COLUMNS_AUTO_SIZE_TO_FIT; // Default column setting
    private boolean fIsTargetLittleEndian = true; // Default target endian setting
    private boolean fIsDisplayLittleEndian = true; // Default display endian setting
    private boolean fEditInserMode = false; // Insert mode:  true = replace existing number, false = overstrike

    // Constructors

    public Rendering(Composite parent, FPRendering renderingParent) {
        super(parent, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL);
        this.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME)); // TODO: internal?
        this.fParent = renderingParent;

        // Initialize the viewport start

        if (fParent.getMemoryBlock() != null) {
            fViewportAddress = fParent.getMemoryBlockStartAddress();

            // The viewport address will be null if memory may be retrieved at any
            // address less than this memory block's base.  If so use the base address.

            if (fViewportAddress == null)
                fViewportAddress = fParent.getBigBaseAddress();

            fBaseAddress = fViewportAddress;
        }

        // Instantiate the panes, TODO default visibility from state or plugin.xml?

        this.fAddressPane = createAddressPane();
        this.fDataPane = createDataPane();

        fAddressBar = new GoToAddressComposite();
        fAddressBarControl = fAddressBar.createControl(parent);
        Button button = fAddressBar.getButton(IDialogConstants.OK_ID);

        if (button != null) {
            button.addSelectionListener(new SelectionAdapter() {

                @Override
                public void widgetSelected(SelectionEvent e) {
                    doGoToAddress();
                }
            });

            button = fAddressBar.getButton(IDialogConstants.CANCEL_ID);
            if (button != null) {
                button.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        setVisibleAddressBar(false);
                    }
                });
            }
        }

        fAddressBar.getExpressionWidget().addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                doGoToAddress();
            }
        });

        fAddressBar.getExpressionWidget().addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.keyCode == SWT.ESC)
                    setVisibleAddressBar(false);
                super.keyPressed(e);
            }
        });

        this.fAddressBarControl.setVisible(false);

        getHorizontalBar().addSelectionListener(createHorizontalBarSelectionListener());
        getVerticalBar().addSelectionListener(createVerticalBarSelectinListener());

        this.addPaintListener(new PaintListener() {
            @Override
            public void paintControl(PaintEvent pe) {
                pe.gc.setBackground(Rendering.this.getFPRendering().getColorBackground());
                pe.gc.fillRectangle(0, 0, Rendering.this.getBounds().width, Rendering.this.getBounds().height);
            }
        });

        setLayout();

        this.addControlListener(new ControlListener() {
            @Override
            public void controlMoved(ControlEvent ce) {
            }

            @Override
            public void controlResized(ControlEvent ce) {
                packColumns();
            }
        });

        DebugPlugin.getDefault().addDebugEventListener(this);
    }

    // Determine how many characters are allowed in the column

    private int charsPerColumn() {
        return fDisplayedPrecision + DECIMAL_POINT_SIZE + SIGN_SIZE + EXPONENT_CHARACTER_SIZE + EXPONENT_VALUE_SIZE
                + fCellPadding;
    }

    // Establish the visible layout of the view

    protected void setLayout() {
        this.setLayout(new Layout() {
            @Override
            public void layout(Composite composite, boolean changed) {
                int xOffset = 0;

                if (Rendering.this.getHorizontalBar().isVisible())
                    xOffset = Rendering.this.getHorizontalBar().getSelection();

                int x = xOffset * -1;
                int y = 0;

                if (fAddressBarControl.isVisible()) {
                    fAddressBarControl.setBounds(0, 0, Rendering.this.getBounds().width,
                            fAddressBarControl.computeSize(100, 30).y); // FIXME
                    // y = fAddressBarControl.getBounds().height;
                }

                if (fAddressPane.isPaneVisible()) {
                    fAddressPane.setBounds(x, y, fAddressPane.computeSize(0, 0).x,
                            Rendering.this.getBounds().height - y);
                    x = fAddressPane.getBounds().x + fAddressPane.getBounds().width;
                }

                if (fDataPane.isPaneVisible()) {
                    fDataPane.setBounds(x, y, fDataPane.computeSize(0, 0).x, Rendering.this.getBounds().height - y);
                    x = fDataPane.getBounds().x + fDataPane.getBounds().width;
                }

                ScrollBar horizontal = Rendering.this.getHorizontalBar();

                horizontal.setVisible(true);
                horizontal.setMinimum(0);
                horizontal.setMaximum(fDataPane.getBounds().x + fDataPane.getBounds().width + xOffset);
                @SuppressWarnings("unused")
                int temp = horizontal.getMaximum();
                horizontal.setThumb(getClientArea().width);
                horizontal.setPageIncrement(40); // TODO ?
                horizontal.setIncrement(20); // TODO ?
            }

            @Override
            protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
                return new Point(100, 100); // dummy data
            }
        });
    }

    // Handles for the caret/cursor movement keys

    protected void handleDownArrow() {
        fViewportAddress = fViewportAddress.add(BigInteger.valueOf(getAddressableCellsPerRow()));
        ensureViewportAddressDisplayable();
        redrawPanes();
    }

    protected void handleUpArrow() {
        fViewportAddress = fViewportAddress.subtract(BigInteger.valueOf(getAddressableCellsPerRow()));
        ensureViewportAddressDisplayable();
        redrawPanes();
    }

    protected void handlePageDown() {
        fViewportAddress = fViewportAddress
                .add(BigInteger.valueOf(getAddressableCellsPerRow() * (Rendering.this.getRowCount() - 1)));
        ensureViewportAddressDisplayable();
        redrawPanes();
    }

    protected void handlePageUp() {
        fViewportAddress = fViewportAddress
                .subtract(BigInteger.valueOf(getAddressableCellsPerRow() * (Rendering.this.getRowCount() - 1)));
        ensureViewportAddressDisplayable();
        redrawPanes();
    }

    protected SelectionListener createHorizontalBarSelectionListener() {
        return new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent se) {
                Rendering.this.layout();
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent se) {
                // Do nothing
            }
        };
    }

    protected SelectionListener createVerticalBarSelectinListener() {
        return new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent se) {
                switch (se.detail) {
                case SWT.ARROW_DOWN:
                    handleDownArrow();
                    break;

                case SWT.PAGE_DOWN:
                    handlePageDown();
                    break;

                case SWT.ARROW_UP:
                    handleUpArrow();
                    break;

                case SWT.PAGE_UP:
                    handlePageUp();
                    break;

                case SWT.SCROLL_LINE:
                    // See: BUG 203068 selection event details broken on GTK < 2.6

                default: {
                    if (getVerticalBar().getSelection() == getVerticalBar().getMinimum()) {
                        // Set view port start address to the start address of the Memory Block
                        fViewportAddress = Rendering.this.getMemoryBlockStartAddress();
                    } else if (getVerticalBar().getSelection() == getVerticalBar().getMaximum()) {
                        // The view port end address should be less or equal to the the end address of the Memory Block
                        // Set view port address to be bigger than the end address of the Memory Block for now
                        // and let ensureViewportAddressDisplayable() to figure out the correct view port start address
                        fViewportAddress = Rendering.this.getMemoryBlockEndAddress();
                    } else {
                        // Figure out the delta, ignore events with no delta
                        int deltaScroll = getVerticalBar().getSelection() - fScrollSelection;
                        if (deltaScroll == 0)
                            break;
                        BigInteger deltaRows = scrollbar2rows(deltaScroll);
                        BigInteger newAddress = fViewportAddress
                                .add(BigInteger.valueOf(getAddressableCellsPerRow()).multiply(deltaRows));
                        fViewportAddress = newAddress;
                    }

                    ensureViewportAddressDisplayable();

                    // Update tooltip; FIXME conversion from slider to scrollbar
                    // getVerticalBar().setToolTipText(Rendering.this.getAddressString(fViewportAddress));

                    // Update the addresses in the Address pane.

                    if (fAddressPane.isPaneVisible())
                        fAddressPane.redraw();

                    redrawPanes();

                    break;
                }
                }
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent se) {
                // do nothing
            }
        };
    }

    protected FPAddressPane createAddressPane() {
        return new FPAddressPane(this);
    }

    protected FPDataPane createDataPane() {
        return new FPDataPane(this);
    }

    public FPRendering getFPRendering() // TODO rename
    {
        return fParent;
    }

    protected void setCaretAddress(BigInteger address) {
        fCaretAddress = address;
    }

    protected BigInteger getCaretAddress() {
        // Return the caret address if it has been set.  Otherwise return the viewport address.
        // When the rendering is first created, the caret is unset until the user clicks somewhere
        // in the rendering. It also reset (unset) when the user gives us a new viewport address

        return (fCaretAddress != null) ? fCaretAddress : fViewportAddress;
    }

    void doGoToAddress() {
        try {
            BigInteger address = fAddressBar.getGoToAddress(this.getMemoryBlockStartAddress(),
                    this.getCaretAddress());
            getFPRendering().gotoAddress(address);
            setVisibleAddressBar(false);
        } catch (NumberFormatException e1) {
            // FIXME log?
        }
    }

    // Ensure that all addresses displayed are within the addressable range
    protected void ensureViewportAddressDisplayable() {
        if (fViewportAddress.compareTo(Rendering.this.getMemoryBlockStartAddress()) < 0) {
            fViewportAddress = Rendering.this.getMemoryBlockStartAddress();
        } else if (getViewportEndAddress().compareTo(getMemoryBlockEndAddress().add(BigInteger.ONE)) > 0) {
            fViewportAddress = getMemoryBlockEndAddress()
                    .subtract(BigInteger.valueOf(getAddressableCellsPerRow() * getRowCount() - 1));
        }

        setScrollSelection();
    }

    public FPIMemorySelection getSelection() {
        return fSelection;
    }

    protected int getHistoryDepth() {
        return fViewportCache.getHistoryDepth();
    }

    protected void setHistoryDepth(int depth) {
        fViewportCache.setHistoryDepth(depth);
    }

    public void logError(String message, Exception e) {
        Status status = new Status(IStatus.ERROR, fParent.getRenderingId(), DebugException.INTERNAL_ERROR, message,
                e);
        FPRenderingPlugin.getDefault().getLog().log(status);
    }

    public void handleFontPreferenceChange(Font font) {
        setFont(font);

        Control controls[] = this.getRenderingPanes();
        for (int index = 0; index < controls.length; index++)
            controls[index].setFont(font);

        packColumns();
        layout(true);
    }

    public void setPaddingString(String padding) {
        fPaddingString = padding;
        refresh();
    }

    public char getPaddingCharacter() {
        return fPaddingString.charAt(0); // use only the first character
    }

    static int suspendCount = 0;

    @Override
    public void handleDebugEvents(DebugEvent[] events) {
        if (this.isDisposed())
            return;

        boolean isChangeOnly = false;
        boolean isSuspend = false;
        boolean isBreakpointHit = false;

        for (int index = 0; index < events.length; index++) {
            if (events[0].getSource() instanceof IDebugElement) {
                final int kind = events[index].getKind();
                final int detail = events[index].getDetail();
                final IDebugElement source = (IDebugElement) events[index].getSource();

                /*
                 * We have to make sure we are comparing memory blocks here. It pretty much is now the
                 * case that the IDebugTarget is always null.  Almost no one in the Embedded Space  is
                 * using anything but CDT/DSF or CDT/TCF at this point. The older CDI stuff will still
                 * be using the old Debug Model API. But this will generate the same memory block  and
                 * a legitimate IDebugTarget which will match properly.
                 */
                if (source.equals(getMemoryBlock())
                        && source.getDebugTarget() == getMemoryBlock().getDebugTarget()) {
                    if ((detail & DebugEvent.BREAKPOINT) != 0)
                        isBreakpointHit = true;

                    if (kind == DebugEvent.SUSPEND) {
                        handleSuspendEvent(detail);
                        isSuspend = true;
                    } else if (kind == DebugEvent.CHANGE) {
                        handleChangeEvent();
                        isChangeOnly = true;
                    }
                }
            }
        }

        if (isSuspend)
            handleSuspend(isBreakpointHit);
        else if (isChangeOnly)
            handleChange();
    }

    protected void handleSuspend(boolean isBreakpointHit) {
        if (getUpdateMode() == UPDATE_ALWAYS || (getUpdateMode() == UPDATE_ON_BREAKPOINT && isBreakpointHit)) {
            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    archiveDeltas();
                    refresh();
                }
            });
        }
    }

    protected void handleChange() {
        if (getUpdateMode() == UPDATE_ALWAYS) {
            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    refresh();
                }
            });
        }
    }

    protected void handleSuspendEvent(int detail) {
    }

    protected void handleChangeEvent() {
    }

    // Return true to enable development debug print statements

    public boolean isDebug() {
        return false;
    }

    protected IMemoryBlockExtension getMemoryBlock() {
        IMemoryBlock block = fParent.getMemoryBlock();
        if (block != null)
            return (IMemoryBlockExtension) block.getAdapter(IMemoryBlockExtension.class);

        return null;
    }

    public BigInteger getBigBaseAddress() {
        return fParent.getBigBaseAddress();
    }

    public int getAddressableSize() {
        return fParent.getAddressableSize();
    }

    protected FPIViewportCache getViewportCache() {
        return fViewportCache;
    }

    public FPMemoryByte[] getBytes(BigInteger address, int bytes) throws DebugException {
        return getViewportCache().getBytes(address, bytes);
    }

    // Default visibility for performance

    ViewportCache fViewportCache = new ViewportCache();

    private interface Request {
    }

    class ViewportCache extends Thread implements FPIViewportCache {
        class ArchiveDeltas implements Request {
        }

        class AddressPair implements Request {
            BigInteger startAddress;
            BigInteger endAddress;

            public AddressPair(BigInteger start, BigInteger end) {
                startAddress = start;
                endAddress = end;
            }

            @Override
            public boolean equals(Object obj) {
                if (obj == null)
                    return false;
                if (obj instanceof AddressPair) {
                    return ((AddressPair) obj).startAddress.equals(startAddress)
                            && ((AddressPair) obj).endAddress.equals(endAddress);
                }

                return false;
            }

            @Override
            public int hashCode() {
                return super.hashCode() + startAddress.hashCode() + endAddress.hashCode();
            }

        }

        class MemoryUnit implements Cloneable {
            BigInteger start;

            BigInteger end;

            FPMemoryByte[] bytes;

            @Override
            public MemoryUnit clone() {
                MemoryUnit b = new MemoryUnit();

                b.start = this.start;
                b.end = this.end;
                b.bytes = new FPMemoryByte[this.bytes.length];
                for (int index = 0; index < this.bytes.length; index++)
                    b.bytes[index] = new FPMemoryByte(this.bytes[index].getValue());

                return b;
            }

            public boolean isValid() {
                return this.start != null && this.end != null && this.bytes != null;
            }
        }

        @SuppressWarnings("hiding")
        private HashMap<BigInteger, FPMemoryByte[]> fEditBuffer = new HashMap<BigInteger, FPMemoryByte[]>();
        private boolean fDisposed = false;
        private Object fLastQueued = null;
        private Vector<Object> fQueue = new Vector<Object>();
        protected MemoryUnit fCache = null;
        protected MemoryUnit fHistoryCache[] = new MemoryUnit[0];
        protected int fHistoryDepth = 0;

        public ViewportCache() {
            start();
        }

        @Override
        public void dispose() {
            fDisposed = true;
            synchronized (fQueue) {
                fQueue.notify();
            }
        }

        public int getHistoryDepth() {
            return fHistoryDepth;
        }

        public void setHistoryDepth(int depth) {
            fHistoryDepth = depth;
            fHistoryCache = new MemoryUnit[fHistoryDepth];
        }

        @Override
        public void refresh() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

            if (fCache != null) {
                queueRequest(fViewportAddress, getViewportEndAddress());
            }
        }

        @Override
        public void archiveDeltas() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

            if (fCache != null) {
                queueRequestArchiveDeltas();
            }
        }

        private void queueRequest(BigInteger startAddress, BigInteger endAddress) {
            AddressPair pair = new AddressPair(startAddress, endAddress);
            queue(pair);
        }

        private void queueRequestArchiveDeltas() {
            ArchiveDeltas archive = new ArchiveDeltas();
            queue(archive);
        }

        private void queue(Object element) {
            synchronized (fQueue) {
                if (!(fQueue.size() > 0 && element.equals(fLastQueued))) {
                    fQueue.addElement(element);
                    fLastQueued = element;
                }
                fQueue.notify();
            }
        }

        @Override
        public void run() {
            while (!fDisposed) {
                AddressPair pair = null;
                boolean archiveDeltas = false;
                synchronized (fQueue) {
                    if (fQueue.size() > 0) {
                        Request request = (Request) fQueue.elementAt(0);
                        Class<?> type = request.getClass();

                        while (fQueue.size() > 0 && type.isInstance(fQueue.elementAt(0))) {
                            request = (Request) fQueue.elementAt(0);
                            fQueue.removeElementAt(0);
                        }

                        if (request instanceof ArchiveDeltas)
                            archiveDeltas = true;
                        else if (request instanceof AddressPair)
                            pair = (AddressPair) request;
                    }
                }

                if (archiveDeltas) {
                    for (int i = fViewportCache.getHistoryDepth() - 1; i > 0; i--)
                        fHistoryCache[i] = fHistoryCache[i - 1];

                    fHistoryCache[0] = fCache.clone();
                } else if (pair != null) {
                    populateCache(pair.startAddress, pair.endAddress);
                } else {
                    synchronized (fQueue) {
                        try {
                            if (fQueue.isEmpty()) {
                                fQueue.wait();
                            }
                        } catch (Exception e) {
                            // do nothing
                        }
                    }
                }
            }
        }

        // Cache memory necessary to paint viewport
        // TODO: user setting to buffer +/- x lines
        // TODO: reuse existing cache? probably only a minor performance gain

        private void populateCache(final BigInteger startAddress, final BigInteger endAddress) {
            try {
                IMemoryBlockExtension memoryBlock = getMemoryBlock();

                BigInteger lengthInBytes = endAddress.subtract(startAddress);
                BigInteger addressableSize = BigInteger.valueOf(getAddressableSize());

                long units = lengthInBytes.divide(addressableSize)
                        .add(lengthInBytes.mod(addressableSize).compareTo(BigInteger.ZERO) > 0 ? BigInteger.ONE
                                : BigInteger.ZERO)
                        .longValue();

                // CDT (and maybe other backends) will call setValue() on these MemoryBlock objects.  We
                // don't want this to happen, because it interferes with this rendering's own change history.
                // Ideally, we should strictly use the back end change notification and history, but it is
                // only guaranteed to work for bytes within the address range of the MemoryBlock.

                MemoryByte readBytes[] = memoryBlock.getBytesFromAddress(startAddress, units);
                FPMemoryByte cachedBytes[] = new FPMemoryByte[readBytes.length];

                for (int index = 0; index < readBytes.length; index++)
                    cachedBytes[index] = new FPMemoryByte(readBytes[index].getValue(), readBytes[index].getFlags());

                // Derive the target endian from the read MemoryBytes.

                if (cachedBytes.length > 0)
                    if (cachedBytes[0].isEndianessKnown())
                        setTargetLittleEndian(!cachedBytes[0].isBigEndian());

                // The first time we execute this method, set the display endianness to the target endianness.

                if (!initialDisplayModeSet) {
                    setDisplayLittleEndian(isTargetLittleEndian());
                    initialDisplayModeSet = true;
                }

                // Re-order bytes within unit to be a sequential byte stream if the endian is already little

                if (isTargetLittleEndian()) {
                    // There isn't an order when the unit size is one, so skip for performance

                    if (addressableSize.compareTo(BigInteger.ONE) != 0) {
                        int unitSize = addressableSize.intValue();
                        FPMemoryByte cachedBytesAsByteSequence[] = new FPMemoryByte[cachedBytes.length];
                        for (int unit = 0; unit < units; unit++) {
                            for (int unitbyte = 0; unitbyte < unitSize; unitbyte++) {
                                cachedBytesAsByteSequence[unit * unitSize + unitbyte] = cachedBytes[unit * unitSize
                                        + unitSize - unitbyte];
                            }
                        }
                        cachedBytes = cachedBytesAsByteSequence;
                    }
                }

                final FPMemoryByte[] cachedBytesFinal = cachedBytes;

                fCache = new MemoryUnit();
                fCache.start = startAddress;
                fCache.end = endAddress;
                fCache.bytes = cachedBytesFinal;

                Display.getDefault().asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        // Generate deltas

                        for (int historyIndex = 0; historyIndex < getHistoryDepth(); historyIndex++) {
                            if (fHistoryCache[historyIndex] != null && fHistoryCache[historyIndex].isValid()) {
                                BigInteger maxStart = startAddress.max(fHistoryCache[historyIndex].start);
                                BigInteger minEnd = endAddress.min(fHistoryCache[historyIndex].end)
                                        .subtract(BigInteger.ONE);

                                BigInteger overlapLength = minEnd.subtract(maxStart);
                                if (overlapLength.compareTo(BigInteger.valueOf(0)) > 0) {
                                    // there is overlap

                                    int offsetIntoOld = maxStart.subtract(fHistoryCache[historyIndex].start)
                                            .intValue();
                                    int offsetIntoNew = maxStart.subtract(startAddress).intValue();

                                    for (int i = overlapLength.intValue(); i >= 0; i--) {
                                        cachedBytesFinal[offsetIntoNew + i].setChanged(historyIndex,
                                                cachedBytesFinal[offsetIntoNew + i]
                                                        .getValue() != fHistoryCache[historyIndex].bytes[offsetIntoOld
                                                                + i].getValue());
                                    }

                                    // There are several scenarios where the history cache must be updated from the data cache, so that when a
                                    // cell is edited the font color changes appropriately. The following code deals with the different cases.

                                    if (historyIndex != 0)
                                        continue;

                                    int dataStart = fCache.start.intValue();
                                    int dataEnd = fCache.end.intValue();
                                    int dataLength = fCache.bytes.length;

                                    int historyStart = fHistoryCache[0].start.intValue();
                                    int historyEnd = fHistoryCache[0].end.intValue();
                                    int historyLength = fHistoryCache[0].bytes.length;

                                    // Case 1: The data cache is smaller than the history cache; the data cache's
                                    //         address range is fully covered by the history cache.  Do nothing.

                                    if ((dataStart >= historyStart) && (dataEnd <= historyEnd))
                                        continue;

                                    // Case 2: The data and history cache's do not overlap at all

                                    if (((dataStart < historyStart) && (dataEnd < historyStart))
                                            || (dataStart > historyEnd)) {
                                        // Create a new history cache: Copy the data cache bytes to the history cache

                                        MemoryUnit newHistoryCache = new MemoryUnit();

                                        newHistoryCache.start = fCache.start;
                                        newHistoryCache.end = fCache.end;
                                        int newHistoryCacheSize = fCache.bytes.length;
                                        newHistoryCache.bytes = new FPMemoryByte[newHistoryCacheSize];

                                        for (int index = 0; index < newHistoryCacheSize; index++)
                                            newHistoryCache.bytes[index] = new FPMemoryByte(
                                                    fCache.bytes[index].getValue());

                                        fHistoryCache[0] = newHistoryCache;

                                        continue;
                                    }

                                    // Case 3: The data cache starts at a lower address than the history cache, but overlaps the history cache

                                    if ((dataStart < historyStart)
                                            && ((dataEnd >= historyStart) && (dataEnd <= historyEnd))) {
                                        // Create a new history cache with the missing data from the main cache and append the old history to it.

                                        int missingDataByteCount = historyStart - dataStart;
                                        int historyCacheSize = historyLength;
                                        int newHistoryCacheSize = missingDataByteCount + historyLength;

                                        if (missingDataByteCount <= 0 && historyCacheSize <= 0)
                                            break;

                                        MemoryUnit newHistoryCache = new MemoryUnit();

                                        newHistoryCache.start = fCache.start;
                                        newHistoryCache.end = fHistoryCache[0].end;
                                        newHistoryCache.bytes = new FPMemoryByte[newHistoryCacheSize];

                                        // Copy the missing bytes from the beginning of the main cache to the history cache.

                                        for (int index = 0; index < missingDataByteCount; index++)
                                            newHistoryCache.bytes[index] = new FPMemoryByte(
                                                    fCache.bytes[index].getValue());

                                        // Copy the remaining bytes from the old history cache to the new history cache

                                        for (int index = 0; index < historyCacheSize; index++)
                                            newHistoryCache.bytes[index + missingDataByteCount] = new FPMemoryByte(
                                                    fHistoryCache[0].bytes[index].getValue());

                                        fHistoryCache[0] = newHistoryCache;

                                        continue;
                                    }

                                    // Case 4: The data cache starts at a higher address than the history cache

                                    if (((dataStart >= historyStart) && (dataStart <= historyEnd))
                                            && (dataEnd > historyEnd)) {
                                        // Append the missing main cache bytes to the history cache.

                                        int missingDataByteCount = dataEnd - historyEnd;
                                        int historyCacheSize = historyEnd - historyStart;
                                        int newHistoryCacheSize = missingDataByteCount + historyLength;

                                        if (missingDataByteCount > 0 && historyCacheSize > 0) {
                                            MemoryUnit newHistoryCache = new MemoryUnit();

                                            newHistoryCache.start = fHistoryCache[0].start;
                                            newHistoryCache.end = fCache.end;
                                            newHistoryCache.bytes = new FPMemoryByte[newHistoryCacheSize];

                                            // Copy the old history bytes to the new history cache

                                            System.arraycopy(fHistoryCache[0].bytes, 0, newHistoryCache.bytes, 0,
                                                    historyLength);

                                            // Copy the bytes from the main cache that are not in the history cache to the end of the new history cache.

                                            for (int index = 0; index < missingDataByteCount; index++) {
                                                int srcIndex = dataLength - missingDataByteCount + index;
                                                int dstIndex = historyLength + index;
                                                newHistoryCache.bytes[dstIndex] = new FPMemoryByte(
                                                        fCache.bytes[srcIndex].getValue());
                                            }

                                            fHistoryCache[0] = newHistoryCache;

                                            continue;
                                        }
                                    }

                                    // Case 5 - The data cache is greater than the history cache and fully covers it

                                    if (dataStart < historyStart && dataEnd > historyEnd) {
                                        int start = 0;
                                        int end = 0;

                                        // Create a new history cache to reflect the entire data cache

                                        MemoryUnit newHistoryCache = new MemoryUnit();

                                        newHistoryCache.start = fCache.start;
                                        newHistoryCache.end = fCache.end;
                                        int newHistoryCacheSize = fCache.bytes.length;
                                        newHistoryCache.bytes = new FPMemoryByte[newHistoryCacheSize];

                                        int topByteCount = historyStart - dataStart;
                                        int bottomByteCount = dataEnd - historyEnd;

                                        // Copy the bytes from the beginning of the data cache to the new history cache

                                        for (int index = 0; index < topByteCount; index++)
                                            newHistoryCache.bytes[index] = new FPMemoryByte(
                                                    fCache.bytes[index].getValue());

                                        // Copy the old history cache bytes to the new history cache

                                        start = topByteCount;
                                        end = topByteCount + historyLength;

                                        for (int index = start; index < end; index++)
                                            newHistoryCache.bytes[index] = new FPMemoryByte(
                                                    fCache.bytes[index].getValue());

                                        // Copy the bytes from the end of the data cache to the new history cache

                                        start = topByteCount + historyLength;
                                        end = topByteCount + historyLength + bottomByteCount;

                                        for (int index = start; index < end; index++)
                                            newHistoryCache.bytes[index] = new FPMemoryByte(
                                                    fCache.bytes[index].getValue());

                                        fHistoryCache[0] = newHistoryCache;

                                        continue;
                                    }
                                }
                            }
                        }

                        // If the history does not exist, populate the history with the just populated
                        // cache.  This solves the use case of (1) connect to target; (2) edit memory
                        // before the first suspend debug event; (3) paint differences in changed color.

                        if (fHistoryCache[0] == null)
                            fHistoryCache[0] = fCache.clone();

                        Rendering.this.redrawPanes();
                    }
                });

            } catch (Exception e) {
                // User can scroll to any memory, whether it's valid on the target or not.  It doesn't make
                // much sense to fill up the Eclipse error log with such "failures."  So, comment out for now.
                // logError(FPRenderingMessages.getString("FAILURE_READ_MEMORY"), e); //$NON-NLS-1$
            }
        }

        // Bytes will be fetched from cache

        @Override
        public FPMemoryByte[] getBytes(BigInteger address, int bytesRequested) throws DebugException {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

            if (containsEditedCell(address)) // Cell size cannot be switched during an edit
                return getEditedMemory(address);

            boolean contains = false;
            if (fCache != null && fCache.start != null) {
                // See if all of the data requested is in the cache

                BigInteger dataEnd = address.add(BigInteger.valueOf(bytesRequested));

                if (fCache.start.compareTo(address) <= 0 && fCache.end.compareTo(dataEnd) >= 0
                        && fCache.bytes.length > 0)
                    contains = true;
            }

            if (contains) {
                int offset = address.subtract(fCache.start).intValue();
                FPMemoryByte bytes[] = new FPMemoryByte[bytesRequested];

                for (int index = 0; index < bytes.length; index++)
                    bytes[index] = fCache.bytes[offset + index];

                return bytes;
            }

            FPMemoryByte bytes[] = new FPMemoryByte[bytesRequested];

            for (int index = 0; index < bytes.length; index++) {
                bytes[index] = new FPMemoryByte();
                bytes[index].setReadable(false);
            }

            fViewportCache.queueRequest(fViewportAddress, getViewportEndAddress());

            return bytes;
        }

        @Override
        public boolean containsEditedCell(BigInteger address) {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$
            return fEditBuffer.containsKey(address);
        }

        public FPMemoryByte[] getEditedMemory(BigInteger address) {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$
            return fEditBuffer.get(address);
        }

        @Override
        public void clearEditBuffer() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$
            fEditBuffer.clear();
            Rendering.this.redrawPanes();
        }

        @Override
        public void writeEditBuffer() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

            Set<BigInteger> keySet = fEditBuffer.keySet();
            Iterator<BigInteger> iterator = keySet.iterator();

            while (iterator.hasNext()) {
                BigInteger address = iterator.next();
                FPMemoryByte[] bytes = fEditBuffer.get(address);

                byte byteValue[] = new byte[bytes.length];

                for (int index = 0; index < bytes.length; index++)
                    byteValue[index] = bytes[index].getValue();

                try {
                    IMemoryBlockExtension block = getMemoryBlock();
                    BigInteger offset = address.subtract(block.getBigBaseAddress());
                    block.setValue(offset, byteValue);
                } catch (Exception e) {
                    MemoryViewUtil.openError(FPRenderingMessages.getString("FAILURE_WRITE_MEMORY"), "", e); //$NON-NLS-1$ //$NON-NLS-2$
                    logError(FPRenderingMessages.getString("FAILURE_WRITE_MEMORY"), e); //$NON-NLS-1$
                }
            }

            clearEditBuffer();
        }

        @Override
        public void setEditedValue(BigInteger address, FPMemoryByte[] bytes) {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : FPRenderingMessages
                    .getString("CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$
            fEditBuffer.put(address, bytes);
            Rendering.this.redrawPanes();
        }
    }

    public void setVisibleAddressBar(boolean visible) {
        fAddressBarControl.setVisible(visible);
        if (visible) {
            String selectedStr = "0x" + getCaretAddress().toString(16); //$NON-NLS-1$
            Text text = fAddressBar.getExpressionWidget();
            text.setText(selectedStr);
            text.setSelection(0, text.getCharCount());
            fAddressBar.getExpressionWidget().setFocus();
        }

        layout(true);
        layoutPanes();
    }

    public void setDirty(boolean needRefresh) {
        fCacheDirty = needRefresh;
    }

    public boolean isDirty() {
        return fCacheDirty;
    }

    @Override
    public void dispose() {
        DebugPlugin.getDefault().removeDebugEventListener(this);
        if (fViewportCache != null) {
            fViewportCache.dispose();
            fViewportCache = null;
        }
        super.dispose();
    }

    class Selection implements FPIMemorySelection {
        private BigInteger fStartHigh = null;
        private BigInteger fStartLow = null;

        private BigInteger fEndHigh = null;
        private BigInteger fEndLow = null;

        @Override
        public void clear() {
            fEndHigh = fEndLow = fStartHigh = fStartLow = null;
            redrawPanes();
        }

        @Override
        public boolean hasSelection() {
            return fStartHigh != null && fStartLow != null && fEndHigh != null && fEndLow != null;
        }

        @Override
        public boolean isSelected(BigInteger address) {
            // Do we have valid start and end addresses?

            if (getEnd() == null || getStart() == null)
                return false;

            // If end is greater than start

            if (getEnd().compareTo(getStart()) >= 0) {
                // If address is greater-than-or-equal-to start and less then end, return true
                if (address.compareTo(getStart()) >= 0 && address.compareTo(getEnd()) < 0)
                    return true;
            }

            // If start is greater than end

            else if (getStart().compareTo(getEnd()) >= 0) {
                // If address is greater-than-or-equal-to zero and less than start, return true
                if (address.compareTo(getEnd()) >= 0 && address.compareTo(getStart()) < 0)
                    return true;
            }

            return false;
        }

        // Set selection start

        @Override
        public void setStart(BigInteger high, BigInteger low) {
            if (high == null && low == null) {
                if (fStartHigh != null && fStartLow != null) {
                    fStartHigh = null;
                    fStartLow = null;
                    redrawPanes();
                }

                return;
            }

            boolean changed = false;

            if (fStartHigh == null || !high.equals(fStartHigh)) {
                fStartHigh = high;
                changed = true;
            }

            if (fStartLow == null || !low.equals(fStartLow)) {
                fStartLow = low;
                changed = true;
            }

            if (changed)
                redrawPanes();
        }

        // Set selection end

        @Override
        public void setEnd(BigInteger high, BigInteger low) {
            if (high == null && low == null) {
                if (fEndHigh != null && fEndLow != null) {
                    fEndHigh = null;
                    fEndLow = null;
                    redrawPanes();
                }

                return;
            }

            boolean changed = false;

            if (fEndHigh == null || !high.equals(fEndHigh)) {
                fEndHigh = high;
                changed = true;
            }

            if (fEndLow == null || !low.equals(fEndLow)) {
                fEndLow = low;
                changed = true;
            }

            if (changed)
                redrawPanes();
        }

        @Override
        public BigInteger getHigh() {
            if (!hasSelection())
                return null;
            return getStart().max(getEnd());
        }

        @Override
        public BigInteger getLow() {
            if (!hasSelection())
                return null;
            return getStart().min(getEnd());
        }

        @Override
        public BigInteger getStart() {
            // If there is no start, return null
            if (fStartHigh == null)
                return null;

            // If there is no end, return the high address of the start
            if (fEndHigh == null)
                return fStartHigh;

            // If Start High/Low equal End High/Low, return a low start and high end
            if (fStartHigh.equals(fEndHigh) && fStartLow.equals(fEndLow))
                return fStartLow;

            BigInteger differenceEndToStartHigh = fEndHigh.subtract(fStartHigh).abs();
            BigInteger differenceEndToStartLow = fEndHigh.subtract(fStartLow).abs();

            // Return the start high or start low based on which creates a larger selection
            if (differenceEndToStartHigh.compareTo(differenceEndToStartLow) > 0)
                return fStartHigh;

            return fStartLow;
        }

        @Override
        public BigInteger getStartLow() {
            return fStartLow;
        }

        @Override
        public BigInteger getEnd() {
            // If there is no end, return null
            if (fEndHigh == null)
                return null;

            // *** Temporary for debugging ***

            if (fStartHigh == null || fStartLow == null) {
                return null;
            }

            // If Start High/Low equal End High/Low, return a low start and high end
            if (fStartHigh.equals(fEndHigh) && fStartLow.equals(fEndLow))
                return fStartHigh;

            BigInteger differenceStartToEndHigh = fStartHigh.subtract(fEndHigh).abs();
            BigInteger differenceStartToEndLow = fStartHigh.subtract(fEndLow).abs();

            // Return the start high or start low based on which creates a larger selection
            if (differenceStartToEndHigh.compareTo(differenceStartToEndLow) >= 0)
                return fEndHigh;

            return fEndLow;
        }
    }

    public void setPaneVisible(int pane, boolean visible) {
        switch (pane) {
        case PANE_ADDRESS:
            fAddressPane.setPaneVisible(visible);
            break;
        case PANE_DATA:
            fDataPane.setPaneVisible(visible);
            break;
        }

        fireSettingsChanged();
        layoutPanes();
    }

    public boolean getPaneVisible(int pane) {
        switch (pane) {
        case PANE_ADDRESS:
            return fAddressPane.isPaneVisible();
        case PANE_DATA:
            return fDataPane.isPaneVisible();
        default:
            return false;
        }
    }

    protected void packColumns() {
        int availableWidth = Rendering.this.getSize().x;

        if (fAddressPane.isPaneVisible()) {
            availableWidth -= fAddressPane.computeSize(0, 0).x;
            availableWidth -= Rendering.this.getRenderSpacing() * 2;
        }

        int combinedWidth = 0;

        if (fDataPane.isPaneVisible())
            combinedWidth += fDataPane.getCellWidth();

        if (getColumnsSetting() == Rendering.COLUMNS_AUTO_SIZE_TO_FIT) {
            if (combinedWidth == 0)
                fColumnCount = 0;
            else {
                fColumnCount = availableWidth / combinedWidth;
                if (fColumnCount == 0)
                    fColumnCount = 1; // Paint one column even if only part can show in view
            }
        } else
            fColumnCount = getColumnsSetting();

        try {
            // Update the number of bytes per row; the max/min scroll range and the current thumbnail position.

            fBytesPerRow = getCharsPerColumn() * getColumnCount();
            getVerticalBar().setMinimum(1);

            // scrollbar maximum range is Integer.MAX_VALUE.

            getVerticalBar().setMaximum(getMaxScrollRange().min(BigInteger.valueOf(Integer.MAX_VALUE)).intValue());
            getVerticalBar().setIncrement(1);
            getVerticalBar().setPageIncrement(this.getRowCount() - 1);

            // FIXME: conversion of slider to scrollbar
            // fScrollBar.setToolTipText(Rendering.this.getAddressString(fViewportAddress));

            setScrollSelection();
        } catch (Exception e) {
            // FIXME precautionary
        }

        Rendering.this.redraw();
        Rendering.this.redrawPanes();
    }

    public FPAbstractPane[] getRenderingPanes() {
        return new FPAbstractPane[] { fAddressPane, fDataPane };
    }

    public int getCellPadding() {
        return fCellPadding;
    }

    protected int getRenderSpacing() {
        return fPaneSpacing;
    }

    public void refresh() {
        if (!this.isDisposed()) {
            if (this.isVisible() && getViewportCache() != null) {
                getViewportCache().refresh();
            } else {
                setDirty(true);
                fParent.updateRenderingLabels();
            }
        }
    }

    protected void archiveDeltas() {
        this.getViewportCache().archiveDeltas();
    }

    public void gotoAddress(BigInteger address) {
        // Ensure that the GoTo address is within the addressable range

        if ((address.compareTo(this.getMemoryBlockStartAddress()) < 0)
                || (address.compareTo(this.getMemoryBlockEndAddress()) > 0))
            return;

        fViewportAddress = address;

        // Reset the caret and selection state (no caret and no selection)

        fCaretAddress = null;
        fSelection = new Selection();

        redrawPanes();
    }

    public void setViewportStartAddress(BigInteger newAddress) {
        fViewportAddress = newAddress;
    }

    public BigInteger getViewportStartAddress() {
        return fViewportAddress;
    }

    public BigInteger getViewportEndAddress() {
        return fViewportAddress
                .add(BigInteger.valueOf(this.getBytesPerRow() * getRowCount() / getAddressableSize()));
    }

    public String getAddressString(BigInteger address) {
        StringBuffer addressString = new StringBuffer(address.toString(16).toUpperCase());

        for (int chars = getAddressBytes() * 2 - addressString.length(); chars > 0; chars--)
            addressString.insert(0, '0');

        addressString.insert(0, "0x"); //$NON-NLS-1$

        return addressString.toString();
    }

    protected int getAddressBytes() {
        return fParent.getAddressSize();
    }

    public Control getAddressBarControl() {
        return fAddressBarControl;
    }

    public int getColumnCount() {
        return fColumnCount;
    }

    public int getColumnsSetting() {
        return fColumnsSetting;
    }

    protected void setBytesPerRow(int count) {
        fBytesPerRow = count;
    }

    protected void setColumnCount(int count) {
        fColumnCount = count;
    }

    public void setColumnsSetting(int columns) {
        if (fColumnsSetting != columns) {
            fColumnsSetting = columns;
            fireSettingsChanged();
            layoutPanes();
        }
    }

    protected void ensureVisible(BigInteger address) {
        BigInteger viewportStart = this.getViewportStartAddress();
        BigInteger viewportEnd = this.getViewportEndAddress();

        boolean isAddressBeforeViewportStart = address.compareTo(viewportStart) < 0;
        boolean isAddressAfterViewportEnd = address.compareTo(viewportEnd) > 0;

        if (isAddressBeforeViewportStart || isAddressAfterViewportEnd)
            gotoAddress(address);
    }

    protected int getRowCount() {
        int rowCount = 0;
        Control panes[] = getRenderingPanes();
        for (int i = 0; i < panes.length; i++)
            if (panes[i] instanceof FPAbstractPane)
                rowCount = Math.max(rowCount, ((FPAbstractPane) panes[i]).getRowCount());

        return rowCount;
    }

    protected int getBytesPerRow() {
        return fBytesPerRow;
    }

    protected int getAddressableCellsPerRow() {
        return getBytesPerRow() / getAddressableSize();
    }

    public int getAddressesPerColumn() {
        return this.getCharsPerColumn() / getAddressableSize();
    }

    /**
     * @return Set current scroll selection
     */
    protected void setScrollSelection() {
        BigInteger selection = getViewportStartAddress().divide(BigInteger.valueOf(getAddressableCellsPerRow()));

        fScrollSelection = rows2scrollbar(selection);
        getVerticalBar().setSelection(fScrollSelection);
    }

    /**
     * compute the maximum scrolling range.
     *
     * @return number of lines that rendering can display
     */
    private BigInteger getMaxScrollRange() {
        BigInteger difference = getMemoryBlockEndAddress().subtract(getMemoryBlockStartAddress())
                .add(BigInteger.ONE);
        BigInteger maxScrollRange = difference.divide(BigInteger.valueOf(getAddressableCellsPerRow()));
        if (maxScrollRange.multiply(BigInteger.valueOf(getAddressableCellsPerRow())).compareTo(difference) != 0)
            maxScrollRange = maxScrollRange.add(BigInteger.ONE);

        // Support targets with an addressable size greater than 1

        maxScrollRange = maxScrollRange.divide(BigInteger.valueOf(getAddressableSize()));
        return maxScrollRange;
    }

    /**
     * The scroll range is limited by SWT. Because it can be less than the number
     * of rows (of memory) that we need to display, we need an arithmetic mapping.
     *
     * @return ratio this function returns how many rows a scroll bar unit
     *         represents. The number will be some fractional value, up to but
     *         not exceeding the value 1. I.e., when the scroll range exceeds
     *         the row range, we use a 1:1 mapping.
     */
    private final BigDecimal getScrollRatio() {
        BigInteger maxRange = getMaxScrollRange();
        if (maxRange.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
            return new BigDecimal(maxRange).divide(BigDecimal.valueOf(Integer.MAX_VALUE),
                    SCROLL_CONVERSION_PRECISION);
        }

        return BigDecimal.ONE;
    }

    /**
     * Convert memory row units to scroll bar units. The scroll range is limited
     * by SWT. Because it can be less than the number of rows (of memory) that
     * we need to display, we need an arithmetic mapping.
     *
     * @param rows
     *            units of memory
     * @return scrollbar units
     */
    private int rows2scrollbar(BigInteger rows) {
        return new BigDecimal(rows).divide(getScrollRatio(), SCROLL_CONVERSION_PRECISION).intValue();
    }

    /**
     * Convert scroll bar units to memory row units. The scroll range is limited
     * by SWT. Because it can be less than the number of rows (of memory) that
     * we need to display, we need an arithmetic mapping.
     *
     * @param scrollbarUnits
     *            scrollbar units
     * @return number of rows of memory
     */
    BigInteger scrollbar2rows(int scrollbarUnits) {
        return getScrollRatio().multiply(BigDecimal.valueOf(scrollbarUnits), SCROLL_CONVERSION_PRECISION)
                .toBigInteger();
    }

    /**
     * @return start address of the memory block
     */
    protected BigInteger getMemoryBlockStartAddress() {
        if (fMemoryBlockStartAddress == null)
            fMemoryBlockStartAddress = fParent.getMemoryBlockStartAddress();
        if (fMemoryBlockStartAddress == null)
            fMemoryBlockStartAddress = BigInteger.ZERO;

        return fMemoryBlockStartAddress;
    }

    /**
     * @return end address of the memory block
     */
    protected BigInteger getMemoryBlockEndAddress() {
        if (fMemoryBlockEndAddress == null)
            fMemoryBlockEndAddress = fParent.getMemoryBlockEndAddress();

        return fMemoryBlockEndAddress;
    }

    public FPDataType getFPDataType() {
        return fFPDataType;
    }

    public void setFPDataType(FPDataType numberType) {
        if (fFPDataType == numberType)
            return;

        fFPDataType = numberType;
        fireSettingsChanged();
        layoutPanes();
    }

    public int getUpdateMode() {
        return fUpdateMode;
    }

    public void setUpdateMode(int fUpdateMode) {
        this.fUpdateMode = fUpdateMode;
    }

    protected String getCharacterSet(int mode) {
        switch (mode) {
        case Rendering.TEXT_UTF8:
            return "UTF8"; //$NON-NLS-1$

        case Rendering.TEXT_UTF16:
            return "UTF16"; //$NON-NLS-1$

        case Rendering.TEXT_USASCII:
            return "US-ASCII"; //$NON-NLS-1$

        case Rendering.TEXT_ISO_8859_1:
        default:
            return "ISO-8859-1"; //$NON-NLS-1$
        }
    }

    public int getBytesPerCharacter() {
        return 1;
    }

    public boolean isTargetLittleEndian() {
        return fIsTargetLittleEndian;
    }

    public void setTargetLittleEndian(boolean littleEndian) {
        if (fIsTargetLittleEndian == littleEndian)
            return;

        fParent.setTargetMemoryLittleEndian(littleEndian);
        fIsTargetLittleEndian = littleEndian;

        Display.getDefault().asyncExec(new Runnable() {
            @Override
            public void run() {
                fireSettingsChanged();
                layoutPanes();
            }
        });
    }

    public boolean isDisplayLittleEndian() {
        return fIsDisplayLittleEndian;
    }

    public void setDisplayLittleEndian(boolean isLittleEndian) {
        if (fIsDisplayLittleEndian == isLittleEndian)
            return;
        fIsDisplayLittleEndian = isLittleEndian;
        fireSettingsChanged();

        Display.getDefault().asyncExec(new Runnable() {
            @Override
            public void run() {
                layoutPanes();
            }
        });
    }

    public int getCharsPerColumn() {
        return fCharsPerColumn;
    }

    public int getDisplayedPrecision() {
        return fDisplayedPrecision;
    }

    // Set the number of precision digits that are displayed in the view

    public void setDisplayedPrecision(int displayedPrecision) {
        if (fDisplayedPrecision != displayedPrecision) {
            fDisplayedPrecision = displayedPrecision;
            fCharsPerColumn = charsPerColumn();
            fireSettingsChanged();
            layoutPanes();
        }
    }

    protected void redrawPane(int paneId) {
        if (!isDisposed() && this.isVisible()) {
            FPAbstractPane pane = null;

            if (paneId == Rendering.PANE_ADDRESS) {
                pane = fAddressPane;
            } else if (paneId == Rendering.PANE_DATA) {
                pane = fDataPane;
            }

            if (pane != null && pane.isPaneVisible()) {
                pane.redraw();
                pane.setRowCount();
                if (pane.isFocusControl())
                    pane.updateTheCaret();
            }
        }

        fParent.updateRenderingLabels();
    }

    protected void redrawPanes() {
        if (!isDisposed() && this.isVisible()) {
            if (fAddressPane.isPaneVisible()) {
                fAddressPane.redraw();
                fAddressPane.setRowCount();
                if (fAddressPane.isFocusControl())
                    fAddressPane.updateTheCaret();
            }

            if (fDataPane.isPaneVisible()) {
                fDataPane.redraw();
                fDataPane.setRowCount();
                if (fDataPane.isFocusControl())
                    fDataPane.updateTheCaret();
            }
        }

        fParent.updateRenderingLabels();
    }

    void layoutPanes() {
        packColumns();
        layout(true);

        redraw();
        redrawPanes();
    }

    void fireSettingsChanged() {
        fAddressPane.settingsChanged();
        fDataPane.settingsChanged();
    }

    protected void copyAddressToClipboard() {
        Clipboard clip = null;

        try {
            clip = new Clipboard(getDisplay());

            String addressString = "0x" + getCaretAddress().toString(16); //$NON-NLS-1$

            TextTransfer plainTextTransfer = TextTransfer.getInstance();
            clip.setContents(new Object[] { addressString }, new Transfer[] { plainTextTransfer });
        } finally {
            if (clip != null) {
                clip.dispose();
            }
        }
    }

    // Given an array of bytes, the data type and endianness, return a scientific notation string representation

    public String sciNotationString(FPMemoryByte byteArray[], FPDataType fpDataType, boolean isLittleEndian) {
        StringBuffer textString = null;

        // Check the byte array for readability

        for (int index = 0; index < byteArray.length; index++)
            if (!byteArray[index].isReadable())
                return FPutilities.fillString(fCharsPerColumn, '?');

        // Convert the byte array to a floating point value and check to see if it's within the range
        // of the data type.  If the valid range is exceeded, set string to "-Infinity" or "Infinity".

        ByteOrder byteOrder = isLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;

        if (fpDataType == FPDataType.FLOAT) {
            float floatValue = ByteBuffer.wrap(FPutilities.memoryBytesToByteArray(byteArray)).order(byteOrder)
                    .getFloat();
            floatValue = FPutilities.floatLimitCheck(floatValue);

            if (floatValue == Float.NEGATIVE_INFINITY)
                textString = new StringBuffer("-Infinity"); //$NON-NLS-1$

            if (floatValue == Float.POSITIVE_INFINITY)
                textString = new StringBuffer(" Infinity"); //$NON-NLS-1$
        }

        if (fpDataType == FPDataType.DOUBLE) {
            double doubleValue = ByteBuffer.wrap(FPutilities.memoryBytesToByteArray(byteArray)).order(byteOrder)
                    .getDouble();
            doubleValue = FPutilities.doubleLimitCheck(doubleValue);

            if (doubleValue == Double.NEGATIVE_INFINITY)
                textString = new StringBuffer("-Infinity"); //$NON-NLS-1$

            if (doubleValue == Double.POSITIVE_INFINITY)
                textString = new StringBuffer(" Infinity"); //$NON-NLS-1$
        }

        // If we do not already have a StringBuffer value, Convert the value to
        // a string.  In any case, pad the string with spaces to the cell width.

        if (textString == null)
            textString = new StringBuffer(
                    FPutilities.byteArrayToSciNotation(fpDataType, isLittleEndian, byteArray, fDisplayedPrecision));

        return (textString
                .append(FPutilities.fillString(fCharsPerColumn - textString.length(), getPaddingCharacter())))
                        .toString();
    }

    // Convert the floating point edit buffer string to a byte array and update memory

    public void convertAndUpdateCell(BigInteger memoryAddress, String editBuffer) {
        if (editBuffer != null && editBuffer.length() > 0) {
            // Convert the edit buffer string to byte arrays

            byte[] targetByteArray = new byte[getFPDataType().getByteLength()];

            targetByteArray = FPutilities.floatingStringToByteArray(getFPDataType(), editBuffer,
                    getFPDataType().getBitsize());

            // If we're in little endian mode, reverse the bytes, which is required
            // due to lower-level internal conversion when written to memory.

            if (fIsDisplayLittleEndian)
                targetByteArray = FPutilities.reverseByteOrder(targetByteArray);

            FPMemoryByte[] newMemoryBytes = new FPMemoryByte[targetByteArray.length];

            for (int index = 0; index < targetByteArray.length; index++) {
                newMemoryBytes[index] = new FPMemoryByte(targetByteArray[index]);
                newMemoryBytes[index].setBigEndian(!isTargetLittleEndian());
                newMemoryBytes[index].setEdited(true);
                newMemoryBytes[index].setChanged(true);
            }

            //  Apply the change and make it visible in the view

            getViewportCache().setEditedValue(memoryAddress, newMemoryBytes);
            getViewportCache().writeEditBuffer();
            redraw();
        } else {
            // The edit buffer string is null or has a zero length.

            final String errorText = NLS.bind(FPRenderingMessages.getString("FPRendering.ERROR_FPENTRY_POPUP_TEXT"), //$NON-NLS-1$
                    "<blanks>"); //$NON-NLS-1$

            try {
                // Restore the previous value
                setEditBuffer(new StringBuffer(
                        fDataPane.bytesToSciNotation(getBytes(fCaretAddress, 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"), //$NON-NLS-1$
                    errorText, status);
        }
    }

    // Getter/setter for Insert Mode state

    public void setInsertMode(boolean insertMode) {
        this.fEditInserMode = insertMode;
    }

    public boolean insertMode() {
        return fEditInserMode;
    }

    // Getter/setter for cell edit state:  true = we're in cell edit mode; false = we're not in cell edit mode

    public boolean isEditingCell() {
        return fCellEditState;
    }

    public void setEditingCell(boolean state) {
        this.fCellEditState = state;
    }

    // Getter/setter for the address of the cell currently being edited

    public BigInteger getCellEditAddress() {
        return cellEditAddress;
    }

    public void setCellEditAddress(BigInteger cellEditAddress) {
        this.cellEditAddress = cellEditAddress;
    }

    // Getter/Setter for the memory address of the cell currently being edited

    public BigInteger getMemoryAddress() {
        return memoryAddress;
    }

    public void setMemoryAddress(BigInteger memoryAddress) {
        this.memoryAddress = memoryAddress;
    }

    // Getter/setter for storing the raw/uninterpreted/not-converted-to-scientific-notation text entry string

    public StringBuffer getEditBuffer() {
        return fEditBuffer;
    }

    public void setEditBuffer(StringBuffer cellChars) {
        this.fEditBuffer = cellChars;
    }

    // Enter cell-edit mode

    public void startCellEditing(BigInteger cellAddress, BigInteger memoryAddress, String editString) {
        setEditBuffer(new StringBuffer(editString));
        setCellEditAddress(cellAddress);
        setMemoryAddress(memoryAddress);
        setEditingCell(true);
    }

    // Exit cell-edit mode

    public void endCellEditing() {
        setEditingCell(false);
        setCellEditAddress(null);
        setMemoryAddress(null);
        setEditBuffer(null);

    }

    // Floating point number edit mode status-line display: 'true' = display edit mode, 'false' clear edit mode

    public void displayEditModeIndicator(final boolean indicatorON) {
        UIJob job = new UIJob("FP Renderer Edit Indicator") //$NON-NLS-1$
        {
            @Override
            public IStatus runInUIThread(IProgressMonitor monitor) {
                String statusLineMessage;
                IViewPart viewInstance = null;

                if (indicatorON) {
                    // Construct the edit mode message
                    statusLineMessage = NLS.bind(FPRenderingMessages.getString("FPRendering.EDIT_MODE"), //$NON-NLS-1$
                            (insertMode() ? FPRenderingMessages.getString("FPRendering.EDIT_MODE_INSERT") : //$NON-NLS-1$
                    FPRenderingMessages.getString("FPRendering.EDIT_MODE_OVERWRITE"))); //$NON-NLS-1$
                } else {
                    // 'null' = clear the message
                    statusLineMessage = null;
                }

                // Get the window and page references

                IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                if (window == null)
                    return Status.OK_STATUS;

                IWorkbenchPage page = window.getActivePage();
                if (page == null)
                    return Status.OK_STATUS;

                // Update (or clear) the Workbench status line when the Memory and Memory Browser views are in focus

                viewInstance = page.findView("org.eclipse.debug.ui.MemoryView"); // TODO:  programmatically retrieve ID //$NON-NLS-1$

                if (viewInstance != null)
                    viewInstance.getViewSite().getActionBars().getStatusLineManager().setMessage(statusLineMessage);

                viewInstance = page.findView("org.eclipse.cdt.debug.ui.memory.memorybrowser.MemoryBrowser"); // TODO:  programmatically retrieve ID //$NON-NLS-1$

                if (viewInstance != null)
                    viewInstance.getViewSite().getActionBars().getStatusLineManager().setMessage(statusLineMessage);

                return Status.OK_STATUS;
            }
        };

        job.setSystem(true);
        job.schedule();
    }

    // Calculate memory address from the cell address

    public BigInteger cellToMemoryAddress(BigInteger cellAddress) {
        BigInteger vpStart = getViewportStartAddress();
        BigInteger colChars = BigInteger.valueOf(getCharsPerColumn());
        BigInteger dtBytes = BigInteger.valueOf(getFPDataType().getByteLength());

        return vpStart.add(((cellAddress.subtract(vpStart)).divide(colChars)).multiply(dtBytes));
    }
}