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

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2006, 2010 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
 *******************************************************************************/

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

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

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.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;

@SuppressWarnings("restriction")
public class Rendering extends Composite implements IDebugEventSetListener {
    // the IMemoryRendering parent
    private TraditionalRendering fParent;

    // controls

    protected AddressPane fAddressPane;

    protected DataPane fBinaryPane;

    protected TextPane fTextPane;

    private GoToAddressComposite fAddressBar;

    protected Control fAddressBarControl;

    private Selection fSelection = new Selection();

    // storage

    BigInteger fViewportAddress = null; // default visibility for performance

    BigInteger fMemoryBlockStartAddress = null;
    BigInteger fMemoryBlockEndAddress = null;

    protected BigInteger fBaseAddress = null; // remember the base address

    protected int fColumnCount = 0; // auto calculate can be disabled by user,
    // making this user settable

    protected int fBytesPerRow = 0; // current number of bytes per row are displayed

    private int fCurrentScrollSelection = 0; // current scroll selection;

    private BigInteger fCaretAddress;

    // user settings

    private int fTextMode = 1; // ASCII default, TODO make preference?

    private int fBytesPerColumn = 4; // 4 byte cell width default

    private int fRadix = RADIX_HEX;

    private int fColumnsSetting = COLUMNS_AUTO_SIZE_TO_FIT;

    private boolean fIsTargetLittleEndian = false;

    private boolean fIsDisplayLittleEndian = false;

    // constants used to identify radix
    public final static int RADIX_HEX = 1;

    public final static int RADIX_DECIMAL_SIGNED = 2;

    public final static int RADIX_DECIMAL_UNSIGNED = 3;

    public final static int RADIX_OCTAL = 4;

    public final static int RADIX_BINARY = 5;

    // constants used to identify panes
    public final static int PANE_ADDRESS = 1;

    public final static int PANE_BINARY = 2;

    public final static int PANE_TEXT = 3;

    /**
     * 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;

    public Rendering(Composite parent, TraditionalRendering 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();

            // this 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.fBinaryPane = createDataPane();
        this.fTextPane = createTextPane();

        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() {
            public void paintControl(PaintEvent pe) {
                pe.gc.setBackground(Rendering.this.getTraditionalRendering().getColorBackground());
                pe.gc.fillRectangle(0, 0, Rendering.this.getBounds().width, Rendering.this.getBounds().height);
            }
        });

        setLayout();

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

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

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

    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 (fBinaryPane.isPaneVisible()) {
                    fBinaryPane.setBounds(x, y, fBinaryPane.computeSize(0, 0).x,
                            Rendering.this.getBounds().height - y);
                    x = fBinaryPane.getBounds().x + fBinaryPane.getBounds().width;
                }

                if (fTextPane.isPaneVisible()) {
                    fTextPane.setBounds(x, y,
                            Math.max(fTextPane.computeSize(0, 0).x,
                                    Rendering.this.getClientArea().width - x - xOffset),
                            Rendering.this.getBounds().height - y);
                }

                if (getClientArea().width >= fTextPane.getBounds().x + fTextPane.getBounds().width + xOffset) {
                    Rendering.this.getHorizontalBar().setVisible(false);
                } else {
                    ScrollBar horizontal = Rendering.this.getHorizontalBar();

                    horizontal.setVisible(true);
                    horizontal.setMinimum(0);
                    horizontal.setMaximum(fTextPane.getBounds().x + fTextPane.getBounds().width + xOffset);
                    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
            }
        });
    }

    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() {
            public void widgetSelected(SelectionEvent se) {
                Rendering.this.layout();
            }

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

    protected SelectionListener createVerticalBarSelectinListener() {
        return new SelectionListener() {
            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() - fCurrentScrollSelection;
                        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 on the Address pane. 
                    if (fAddressPane.isPaneVisible()) {
                        fAddressPane.redraw();
                    }
                    redrawPanes();
                    break;
                }

            }

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

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

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

    protected TextPane createTextPane() {
        return new TextPane(this);
    }

    public TraditionalRendering getTraditionalRendering() // 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;
    }

    private void doGoToAddress() {
        try {
            BigInteger address = fAddressBar.getGoToAddress(this.getMemoryBlockStartAddress(),
                    this.getCaretAddress());
            getTraditionalRendering().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));
        }

        setCurrentScrollSelection();
    }

    public IMemorySelection 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);

        TraditionalRenderingPlugin.getDefault().getLog().log(status);
    }

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

        Control controls[] = this.getRenderingPanes();
        for (int i = 0; i < controls.length; i++)
            controls[i].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;

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

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

        for (int i = 0; i < events.length; i++) {
            if (events[0].getSource() instanceof IDebugElement) {
                final int kind = events[i].getKind();
                final int detail = events[i].getDetail();
                final IDebugElement source = (IDebugElement) events[i].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() {
                public void run() {
                    archiveDeltas();
                    refresh();
                }
            });
        }
    }

    protected void handleChange() {
        if (getUpdateMode() == UPDATE_ALWAYS) {
            Display.getDefault().asyncExec(new Runnable() {
                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 IViewportCache getViewportCache() {
        return fViewportCache;
    }

    public TraditionalMemoryByte[] 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 IViewportCache {
        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;
            }

        }

        class MemoryUnit {
            BigInteger start;

            BigInteger end;

            TraditionalMemoryByte[] bytes;

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

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

                return b;
            }

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

        private HashMap<BigInteger, TraditionalMemoryByte[]> fEditBuffer = new HashMap<BigInteger, TraditionalMemoryByte[]>();

        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();
        }

        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];
        }

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

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

        public void archiveDeltas() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.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);

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

                // derive the target endian from the read MemoryBytes.
                if (cachedBytes.length > 0) {
                    if (cachedBytes[0].isEndianessKnown()) {
                        setTargetLittleEndian(!cachedBytes[0].isBigEndian());
                    }
                }

                // reorder 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();
                        TraditionalMemoryByte cachedBytesAsByteSequence[] = new TraditionalMemoryByte[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 TraditionalMemoryByte[] cachedBytesFinal = cachedBytes;

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

                Display.getDefault().asyncExec(new Runnable() {
                    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.valueOf(1));

                                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());
                                    }
                                }
                            }
                        }

                        // 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. Doesn't make much sense to fill up the Eclipse
                // error log with such "failures".
                //                logError(
                //                    TraditionalRenderingMessages
                //                        .getString("TraditionalRendering.FAILURE_READ_MEMORY"), e); //$NON-NLS-1$
            }
        }

        // bytes will be fetched from cache
        public TraditionalMemoryByte[] getBytes(BigInteger address, int bytesRequested) throws DebugException {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.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();
                TraditionalMemoryByte bytes[] = new TraditionalMemoryByte[bytesRequested];
                for (int i = 0; i < bytes.length; i++) {
                    bytes[i] = fCache.bytes[offset + i];
                }

                return bytes;
            }

            TraditionalMemoryByte bytes[] = new TraditionalMemoryByte[bytesRequested];
            for (int i = 0; i < bytes.length; i++) {
                bytes[i] = new TraditionalMemoryByte();
                bytes[i].setReadable(false);
            }

            fViewportCache.queueRequest(fViewportAddress, getViewportEndAddress());

            return bytes;
        }

        public boolean containsEditedCell(BigInteger address) {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

            return fEditBuffer.containsKey(address);
        }

        private TraditionalMemoryByte[] getEditedMemory(BigInteger address) {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

            return fEditBuffer.get(address);
        }

        public void clearEditBuffer() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$

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

        public void writeEditBuffer() {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.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();
                TraditionalMemoryByte[] bytes = fEditBuffer.get(address);

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

                try {
                    IMemoryBlockExtension block = getMemoryBlock();
                    BigInteger offset = address.subtract(block.getBigBaseAddress());
                    block.setValue(offset, byteValue);
                } catch (Exception e) {
                    MemoryViewUtil.openError(
                            TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_WRITE_MEMORY"), "",
                            e);

                    logError(TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_WRITE_MEMORY"), //$NON-NLS-1$
                            e);
                }
            }

            clearEditBuffer();
        }

        public void setEditedValue(BigInteger address, TraditionalMemoryByte[] bytes) {
            assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages
                    .getString("TraditionalRendering.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);
            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 IMemorySelection {
        private BigInteger fStartHigh;
        private BigInteger fStartLow;

        private BigInteger fEndHigh;
        private BigInteger fEndLow;

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

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

        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.compareTo(getStart()) >= 0 && address.compareTo(getEnd()) < 0)
                    return true;
            }
            // if start is greater than end
            else if (getStart().compareTo(getEnd()) >= 0) {
                if (address.compareTo(getEnd()) >= 0 && address.compareTo(getStart()) < 0)
                    return true;
            }

            return false;
        }

        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 || !fStartHigh.equals(high)) {
                fStartHigh = high;
                changed = true;
            }

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

            if (changed)
                redrawPanes();
        }

        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 || !fEndHigh.equals(high)) {
                fEndHigh = high;
                changed = true;
            }

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

            if (changed)
                redrawPanes();
        }

        public BigInteger getHigh() {
            if (!hasSelection())
                return null;

            return getStart().max(getEnd());
        }

        public BigInteger getLow() {
            if (!hasSelection())
                return null;

            return getStart().min(getEnd());
        }

        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;
            else
                return fStartLow;
        }

        public BigInteger getStartLow() {
            return fStartLow;
        }

        public BigInteger getEnd() {
            // if there is no end, return null
            if (fEndHigh == 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;
            else
                return fEndLow;
        }
    }

    public void setPaneVisible(int pane, boolean visible) {
        switch (pane) {
        case PANE_ADDRESS:
            fAddressPane.setPaneVisible(visible);
            break;
        case PANE_BINARY:
            fBinaryPane.setPaneVisible(visible);
            break;
        case PANE_TEXT:
            fTextPane.setPaneVisible(visible);
            break;
        }

        fireSettingsChanged();
        layoutPanes();
    }

    public boolean getPaneVisible(int pane) {
        switch (pane) {
        case PANE_ADDRESS:
            return fAddressPane.isPaneVisible();
        case PANE_BINARY:
            return fBinaryPane.isPaneVisible();
        case PANE_TEXT:
            return fTextPane.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 (fBinaryPane.isPaneVisible())
            combinedWidth += fBinaryPane.getCellWidth();

        if (fTextPane.isPaneVisible())
            combinedWidth += fTextPane.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 and min scroll range and the current thumb nail position.        
            fBytesPerRow = getBytesPerColumn() * 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);
            //TW FIXME conversion of slider to scrollbar
            // fScrollBar.setToolTipText(Rendering.this.getAddressString(fViewportAddress));
            setCurrentScrollSelection();
        } catch (Exception e) {
            // FIXME precautionary
        }

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

    public AbstractPane[] getRenderingPanes() {
        return new AbstractPane[] { fAddressPane, fBinaryPane, fTextPane };
    }

    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 AbstractPane)
                rowCount = Math.max(rowCount, ((AbstractPane) panes[i]).getRowCount());
        }

        return rowCount;
    }

    public int getBytesPerColumn() {
        return fBytesPerColumn;
    }

    protected int getBytesPerRow() {
        return fBytesPerRow;
    }

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

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

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

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

    /**
     * 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);
        } else {
            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
     */
    private 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 int getRadix() {
        return fRadix;
    }

    protected int getNumericRadix(int radix) {
        switch (radix) {
        case RADIX_BINARY:
            return 2;
        case RADIX_OCTAL:
            return 8;
        case RADIX_DECIMAL_SIGNED:
        case RADIX_DECIMAL_UNSIGNED:
            return 10;
        case RADIX_HEX:
            return 16;
        }

        return -1;
    }

    public void setRadix(int mode) {
        if (fRadix == mode)
            return;

        fRadix = mode;
        fireSettingsChanged();
        layoutPanes();
    }

    public void setTextMode(int mode) {
        fTextMode = mode;

        fireSettingsChanged();
        layoutPanes();
    }

    public int getTextMode() {
        return fTextMode;
    }

    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";
        case Rendering.TEXT_UTF16:
            return "UTF16";
        case Rendering.TEXT_USASCII:
            return "US-ASCII";
        case Rendering.TEXT_ISO_8859_1:
        default:
            return "ISO-8859-1";
        }
    }

    public int getBytesPerCharacter() {
        if (fTextMode == Rendering.TEXT_UTF16)
            return 2;

        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() {
            public void run() {
                fireSettingsChanged();
                layoutPanes();
            }
        });
    }

    public boolean isDisplayLittleEndian() {
        return fIsDisplayLittleEndian;
    }

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

        fIsDisplayLittleEndian = littleEndian;

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

    public void setBytesPerColumn(int byteCount) {
        if (fBytesPerColumn != byteCount) {
            fBytesPerColumn = byteCount;
            fireSettingsChanged();
            layoutPanes();
        }
    }

    protected void redrawPane(int paneId) {
        if (!isDisposed() && this.isVisible()) {
            AbstractPane pane = null;
            if (paneId == Rendering.PANE_ADDRESS) {
                pane = fAddressPane;
            } else if (paneId == Rendering.PANE_BINARY) {
                pane = fBinaryPane;
            }
            if (paneId == Rendering.PANE_TEXT) {
                pane = fTextPane;
            }
            if (pane != null && pane.isPaneVisible()) {
                pane.redraw();
                pane.setRowCount();
                if (pane.isFocusControl())
                    pane.updateCaret();
            }

        }

        fParent.updateRenderingLabels();
    }

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

            if (fBinaryPane.isPaneVisible()) {
                fBinaryPane.redraw();
                fBinaryPane.setRowCount();
                if (fBinaryPane.isFocusControl())
                    fBinaryPane.updateCaret();
            }

            if (fTextPane.isPaneVisible()) {
                fTextPane.redraw();
                fTextPane.setRowCount();
                if (fTextPane.isFocusControl())
                    fTextPane.updateCaret();
            }
        }

        fParent.updateRenderingLabels();
    }

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

        redraw();
        redrawPanes();
    }

    private void fireSettingsChanged() {
        fAddressPane.settingsChanged();
        fBinaryPane.settingsChanged();
        fTextPane.settingsChanged();
    }

    protected void copyAddressToClipboard() {
        Clipboard clip = null;
        try {
            clip = new Clipboard(getDisplay());

            String addressString = "0x" + getCaretAddress().toString(16);

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

    static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
            'F' };

    public String getRadixText(MemoryByte bytes[], int radix, boolean isLittleEndian) {
        boolean readableByte = false;
        boolean allBytesReadable = true;
        for (int i = 0; i < bytes.length; i++) {
            if (!bytes[i].isReadable()) {
                allBytesReadable = false;
            } else {
                readableByte = true;
            }
        }

        // convert byte to character if all bytes are readable or
        // it is a mixed of readable&non-readable bytes and format is Hex or Binary. Bugzilla 342239              
        if (allBytesReadable || readableByte && (radix == Rendering.RADIX_HEX || radix == Rendering.RADIX_BINARY)) {
            // bytes from the cache are stored as a sequential byte sequence regardless of target endian.
            // the endian attribute tells us the recommended endian for display. the user may change this
            // from the default. if the endian is little, we must swap the byte order.
            boolean needsSwap = false;
            if (isDisplayLittleEndian())
                needsSwap = true;

            switch (radix) {
            case Rendering.RADIX_HEX:
            case Rendering.RADIX_OCTAL:
            case Rendering.RADIX_BINARY: {
                long value = 0;
                if (needsSwap) {
                    for (int i = 0; i < bytes.length; i++) {
                        value = value << 8;
                        value = value | (bytes[bytes.length - 1 - i].getValue() & 0xFF);
                    }
                } else {
                    for (int i = 0; i < bytes.length; i++) {
                        value = value << 8;
                        value = value | (bytes[i].getValue() & 0xFF);
                    }
                }

                char buf[] = new char[getRadixCharacterCount(radix, bytes.length)];

                switch (radix) {
                case Rendering.RADIX_BINARY: {
                    for (int i = buf.length - 1; i >= 0; i--) {
                        int byteIndex = needsSwap ? bytes.length - 1 - i / 8 : i / 8;
                        if (bytes[byteIndex].isReadable()) {
                            buf[i] = hexdigits[(int) (value & 1)];
                        } else {
                            buf[i] = getPaddingCharacter();
                        }
                        value = value >>> 1;
                    }
                    break;
                }
                case Rendering.RADIX_OCTAL: {
                    for (int i = buf.length - 1; i >= 0; i--) {
                        buf[i] = hexdigits[(int) (value & 7)];
                        value = value >>> 3;
                    }
                    break;
                }
                case Rendering.RADIX_HEX: {
                    for (int i = buf.length - 1; i >= 0; i--) {
                        int byteIndex = needsSwap ? bytes.length - 1 - i / 2 : i / 2;
                        if (bytes[byteIndex].isReadable()) {
                            buf[i] = hexdigits[(int) (value & 15)];
                        } else {
                            buf[i] = getPaddingCharacter();
                        }
                        value = value >>> 4;
                    }
                    break;
                }
                }

                return new String(buf);
            }
            case Rendering.RADIX_DECIMAL_UNSIGNED:
            case Rendering.RADIX_DECIMAL_SIGNED: {
                boolean isSignedType = radix == Rendering.RADIX_DECIMAL_SIGNED ? true : false;

                int textWidth = getRadixCharacterCount(radix, bytes.length);

                char buf[] = new char[textWidth];

                byte[] value = new byte[bytes.length + 1];

                if (needsSwap) {
                    for (int i = 0; i < bytes.length; i++) {
                        value[bytes.length - i] = bytes[i].getValue();
                    }
                } else {
                    for (int i = 0; i < bytes.length; i++) {
                        value[i + 1] = bytes[i].getValue();
                    }
                }

                BigInteger bigValue;
                boolean isNegative = false;
                if (isSignedType && (value[1] & 0x80) != 0) {
                    value[0] = -1;
                    isNegative = true;
                    bigValue = new BigInteger(value).abs();
                } else {
                    value[0] = 0;
                    bigValue = new BigInteger(value);
                }

                for (int i = 0; i < textWidth; i++) {
                    BigInteger divideRemainder[] = bigValue.divideAndRemainder(BigInteger.valueOf(10));
                    int remainder = divideRemainder[1].intValue();
                    buf[textWidth - 1 - i] = hexdigits[remainder % 10];
                    bigValue = divideRemainder[0];
                }

                if (isSignedType) {
                    buf[0] = isNegative ? '-' : ' ';
                }

                return new String(buf);
            }
            }
        }

        StringBuffer errorText = new StringBuffer();
        for (int i = getRadixCharacterCount(radix, bytes.length); i > 0; i--)
            errorText.append(getPaddingCharacter());

        return errorText.toString();
    }

    public int getRadixCharacterCount(int radix, int bytes) {
        switch (radix) {
        case Rendering.RADIX_HEX:
            return bytes * 2;
        case Rendering.RADIX_BINARY:
            return bytes * 8;
        case Rendering.RADIX_OCTAL: {
            switch (bytes) {
            case 1:
                return 3;
            case 2:
                return 6;
            case 4:
                return 11;
            case 8:
                return 22;
            }
        }
        case Rendering.RADIX_DECIMAL_UNSIGNED: {
            switch (bytes) {
            case 1:
                return 3;
            case 2:
                return 5;
            case 4:
                return 10;
            case 8:
                return 20;
            }
        }
        case Rendering.RADIX_DECIMAL_SIGNED: {
            switch (bytes) {
            case 1:
                return 4;
            case 2:
                return 6;
            case 4:
                return 11;
            case 8:
                return 21;
            }
        }
        }

        return 0;
    }

    protected static boolean isValidEditCharacter(char character) {
        return (character >= '0' && character <= '9') || (character >= 'a' && character <= 'z')
                || (character >= 'A' && character <= 'Z') || character == '-' || character == ' ';
    }

    public String formatText(MemoryByte[] memoryBytes, boolean isLittleEndian, int textMode) {
        // check memory byte for unreadable bytes
        boolean readable = true;
        for (int i = 0; i < memoryBytes.length; i++)
            if (!memoryBytes[i].isReadable())
                readable = false;

        // if any bytes are not readable, return ?'s
        if (!readable) {
            StringBuffer errorText = new StringBuffer();
            for (int i = memoryBytes.length; i > 0; i--)
                errorText.append(getPaddingCharacter());
            return errorText.toString();
        }

        // TODO
        // does endian mean anything for text? ah, unicode?

        // create byte array from MemoryByte array
        byte bytes[] = new byte[memoryBytes.length];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = memoryBytes[i].getValue();
        }

        // replace invalid characters with '.'
        // maybe there is a way to query the character set for
        // valid characters?

        // replace invalid US-ASCII with '.'
        if (textMode == Rendering.TEXT_USASCII) {
            for (int i = 0; i < bytes.length; i++) {
                int byteValue = bytes[i];
                if (byteValue < 0)
                    byteValue += 256;

                if (byteValue < 0x20 || byteValue > 0x7e)
                    bytes[i] = '.';
            }
        }

        // replace invalid ISO-8859-1 with '.'
        if (textMode == Rendering.TEXT_ISO_8859_1) {
            for (int i = 0; i < bytes.length; i++) {
                int byteValue = bytes[i];
                if (byteValue < 0)
                    byteValue += 256;

                if (byteValue < 0x20 || (byteValue >= 0x7f && byteValue < 0x9f))
                    bytes[i] = '.';
            }
        }

        try {
            // convert bytes to string using desired character set
            StringBuffer buf = new StringBuffer(new String(bytes, this.getCharacterSet(textMode)));

            // pad string to (byte count - string length) with spaces
            for (int i = 0; i < memoryBytes.length - buf.length(); i++)
                buf.append(' ');
            return buf.toString();
        } catch (Exception e) {
            // return ?s the length of byte count
            StringBuffer buf = new StringBuffer();
            for (int i = 0; i < memoryBytes.length - buf.length(); i++)
                buf.append(getPaddingCharacter());
            return buf.toString();
        }
    }

    public AbstractPane incrPane(AbstractPane currentPane, int offset) {
        final List<AbstractPane> panes = Arrays.asList(getRenderingPanes());
        return panes.get((panes.indexOf(currentPane) + offset) % panes.size());
    }
}