sysperfana/heapanalyser/Libraries/UI/HeapCtrlLib/Renderers/HeapDataRenderer.cs
changeset 8 15296fd0af4a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sysperfana/heapanalyser/Libraries/UI/HeapCtrlLib/Renderers/HeapDataRenderer.cs	Tue Jun 15 12:47:20 2010 +0300
@@ -0,0 +1,1231 @@
+/*
+* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* - Redistributions of source code must retain the above copyright notice,
+*   this list of conditions and the following disclaimer.
+* - Redistributions in binary form must reproduce the above copyright notice,
+*   this list of conditions and the following disclaimer in the documentation
+*   and/or other materials provided with the distribution.
+* - Neither the name of Nokia Corporation nor the names of its contributors
+*   may be used to endorse or promote products derived from this software
+*   without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+* POSSIBILITY OF SUCH DAMAGE.
+* 
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+*
+*/
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Windows.Forms;
+using HeapLib;
+using HeapLib.Cells;
+using HeapLib.Array;
+using HeapLib.Relationships;
+using HeapLib.Reconstructor;
+using HeapCtrlLib.Utilities;
+using HeapCtrlLib.Interfaces;
+using HeapCtrlLib.Factories;
+using HeapCtrlLib.Types;
+using SymbianUtils.Range;
+using SymbianUtils.RawItems;
+
+namespace HeapCtrlLib.Renderers
+{
+    internal class HeapDataRenderer : UserControl
+    {
+        #region Delegates & events
+        public delegate void AddressChangeHandler( uint aAddressOld, uint aAddressNew, HeapCell aFirstCell, int aFirstCellIndex );
+        public event AddressChangeHandler AddressChanged;
+        public delegate void CellSelectionHandler( HeapCell aCell );
+        public event CellSelectionHandler CellSelected;
+        public delegate void CellDoubleClickHandler( HeapCell aCell );
+        public event CellDoubleClickHandler CellDoubleClicked;
+        public delegate void CellRightClickedHandler( HeapCell aCell, RawItem aItem, Point aViewerPos );
+        public event CellRightClickedHandler CellRightClicked;
+        #endregion
+
+        #region Constructors & destructor
+        internal HeapDataRenderer()
+        {
+            InitializeComponent();
+            //
+            LoadTypeSet( THeapCtrlRenderingType.EHeapCtrlRenderingTypeByCell );
+            //
+            this.SetStyle( ControlStyles.UserPaint, true );
+            this.SetStyle( ControlStyles.DoubleBuffer, true );
+            this.SetStyle( ControlStyles.AllPaintingInWmPaint, true );
+            this.SetStyle( ControlStyles.ResizeRedraw, true );
+            this.SetStyle( ControlStyles.Selectable, true );
+            //
+            this.MouseWheel += new MouseEventHandler( HeapDataRenderer_MouseWheel );
+        }
+
+        protected override void Dispose( bool disposing )
+        {
+            if  ( disposing )
+            {
+            }
+            base.Dispose( disposing );
+        }
+        #endregion
+
+        #region Component Designer generated code
+        private void InitializeComponent()
+        {
+            this.iLbl_NoContent = new System.Windows.Forms.Label();
+            this.SuspendLayout();
+            // 
+            // iLbl_NoContent
+            // 
+            this.iLbl_NoContent.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.iLbl_NoContent.Location = new System.Drawing.Point( 0, 0 );
+            this.iLbl_NoContent.Name = "iLbl_NoContent";
+            this.iLbl_NoContent.Size = new System.Drawing.Size( 600, 408 );
+            this.iLbl_NoContent.TabIndex = 0;
+            this.iLbl_NoContent.Text = "No Content";
+            this.iLbl_NoContent.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+            // 
+            // HeapDataRenderer
+            // 
+            this.Controls.Add( this.iLbl_NoContent );
+            this.DoubleBuffered = true;
+            this.Name = "HeapDataRenderer";
+            this.Size = new System.Drawing.Size( 600, 408 );
+            this.MouseDown += new System.Windows.Forms.MouseEventHandler( this.HeapDataRenderer_MouseDown );
+            this.MouseMove += new System.Windows.Forms.MouseEventHandler( this.HeapDataRenderer_MouseMove );
+            this.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler( this.HeapDataRenderer_MouseDoubleClick );
+            this.MouseLeave += new System.EventHandler( this.HeapDataRenderer_MouseLeave );
+            this.MouseUp += new System.Windows.Forms.MouseEventHandler( this.HeapDataRenderer_MouseUp );
+            this.KeyDown += new System.Windows.Forms.KeyEventHandler( this.HeapDataRenderer_KeyDown );
+            this.ResumeLayout( false );
+
+        }
+        #endregion
+
+        #region API
+        public void SetupFilters()
+        {
+            if ( !SupportsFiltering )
+            {
+                throw new NotSupportedException();
+            }
+
+            iFactory.Renderers.Content.SetupFilters();
+        }
+
+        public void LoadTypeSet( THeapCtrlRenderingType aType )
+        {
+            iFactory = HeapCtrlLib.Factories.Factory.CreateByType( aType );
+
+            // Set sizes
+            CellBoxSize = iFactory.CellBoxSize( Zoom );
+            CellPadding = iFactory.CellPadding( Zoom );
+
+            // Do init.
+            InitialiseRenderers();
+        }
+
+        public int RowForAddress( uint aAddress )
+        {
+            uint addressOffset = aAddress - Reconstructor.Statistics.HeapAddressStart;
+            uint bytesPerRow = BytesPerRow;
+            int row = (int) ( addressOffset / bytesPerRow );
+            //System.Diagnostics.Debug.WriteLine( "base: 0x" + Reconstructor.Statistics.HeapAddressStart.ToString("x8") + ", offset: 0x" + addressOffset.ToString("x8") + ", addr: 0x" + aAddress.ToString("x8") + ", row: " + row + ", remainder: " + ( addressOffset % bytesPerRow ) + ", bytesPerRow: " + bytesPerRow );
+            //
+            return row;
+        }
+        #endregion
+
+        #region Properties
+        public HeapReconstructor Reconstructor
+        {
+            get
+            {
+                return iReconstructor;
+            }
+            set
+            {
+                iReconstructor = value;
+                if  ( iReconstructor != null )
+                {
+                    iCells = iReconstructor.Data;
+                    InitialiseRenderers();
+                    if  ( iCells.Count > 0 )
+                    {
+                        HeapCell cell = Cells[ 0 ];
+
+                        // Must first set the address, or else this messes up
+                        // VisibleAddressRange...
+                        Address = cell.Address;
+
+                        // ... which is needed by FocusedCell when changing
+                        // the focus.
+                        FocusedCell = cell;
+                    }
+                }
+            }
+        }
+
+        public HeapCellArray Cells
+        {
+            get
+            {
+                return iCells;
+            }
+        }
+
+        public HeapCell FocusedCell
+        {
+            get
+            {
+                return iFocusedCellKeyboard;
+            }
+            set
+            {
+                iFocusedCellKeyboard = value;
+                if  ( iFocusedCellKeyboard != null )
+                {
+                    uint visRangeMin = (uint) VisibleAddressRange.Min;
+                    uint visRangeMax = (uint) VisibleAddressRange.Max - BytesPerRow;
+
+                    // Do we need to move the view?
+                    int currentMinRow = RowForAddress( visRangeMin );
+                    int row = RowForAddress( value.Address );
+                    int currentMaxRow = RowForAddress ( visRangeMax );
+
+                    //
+                    //System.Diagnostics.Debug.WriteLine( "row: " + row + ", minR: " + currentMinRow + "(" + visRangeMin.ToString("x8") + "), maxR: " + currentMaxRow + "(" + visRangeMax.ToString("x8") + ")" );
+                    //System.Diagnostics.Debug.WriteLine( " " );
+
+                    if  ( row < currentMinRow || row > currentMaxRow )
+                    {
+                        Address = (uint) ( Reconstructor.Statistics.HeapAddressStart + ( row * BytesPerRow ) );
+                    }
+                    else
+                    {
+                        Invalidate();
+                    }
+                }
+
+                if  ( CellSelected != null )
+                {
+                    CellSelected( iFocusedCellKeyboard );
+                }
+            }
+        }
+
+        public uint Address
+        {
+            get { return iCellAddress; }
+            set
+            {
+                this.Enabled = ( value > 0 );
+                iLbl_NoContent.Visible = ( value == 0 );
+
+                // Validate
+                int index = -1;
+                HeapCell cell = Cells.CellByAddress( value, out index );
+                if  ( cell != null )
+                {
+                    iFactory.PopupManager.PopupHide();
+                    //
+                    iCellIndex = index;
+                    uint oldAddress = iCellAddress;
+                    iCellAddress = value;
+                    iFocusedCellMouse = null;
+                    //
+                    if  ( AddressChanged != null )
+                    {
+                        AddressChanged( oldAddress, iCellAddress, cell, iCellIndex );
+                    }
+                    //
+                    Invalidate();
+                }
+                else if ( value == 0 )
+                {
+                    // Do nothing, we're disabled
+                }
+                else
+                {
+                    // Bad address
+                    throw new ArgumentException( "Invalid cell address", "value" );
+                }
+            }
+        }
+
+        public uint BytesPerRow
+        {
+            get
+            {
+                uint bytesPerRow = (uint) RowsAndColumns.Width * 4;
+                return bytesPerRow;
+            }
+        }
+
+        public uint BytesPerScreen
+        {
+            get
+            {
+                uint bytesPerScreen = BytesPerRow * (uint) RowsAndColumns.Height;
+                return bytesPerScreen;
+            }
+        }
+
+        public THeapCtrlZoom Zoom
+        {
+            get { return iZoom; }
+            set
+            {
+                if ( value != iZoom )
+                {
+                    // Get base size from factory
+                    CellBoxSize = iFactory.CellBoxSize( value );
+                    CellPadding = iFactory.CellPadding( value );
+                }
+                //
+                iZoom = value;
+            }
+        }
+
+        public AddressRange VisibleAddressRange
+        {
+            get
+            {
+                AddressRange range = new AddressRange();
+                //
+                range.UpdateMin( Address );
+                range.UpdateMax( Address + BytesPerScreen );
+                //
+                return range;
+            }
+        }
+
+        public bool SupportsFiltering
+        {
+            get { return iFactory.Renderers.Content.SupportsFiltering; }
+        }
+
+        public HeapCellArrayBase BreadcrumbCellsOutgoing
+        {
+            get { return iBreadcrumbCellsOutgoing; }
+            set { iBreadcrumbCellsOutgoing = new HeapCellArrayUnsorted( value ); }
+        }
+
+        public HeapCellArrayBase BreadcrumbCellsIncoming
+        {
+            get { return iBreadcrumbCellsIncoming; }
+            set { iBreadcrumbCellsIncoming = new HeapCellArrayUnsorted( value ); }
+        }
+
+        internal Factory Factory
+        {
+            get { return iFactory; }
+        }
+        #endregion
+
+        #region Co-ordinate mapping
+        public Point AddressToPixelCoordinate( uint aAddress )
+        {
+            Point ret = new Point( 0, 0 );
+            //
+            Size overallPaddingSize = RowsAndColumnsRemainingPixels;
+            Size rowAndColumnDimensions = RowsAndColumns;
+
+            // First, work out how many boxes would be required to draw
+            // the specific cell address
+            long delta = ((long) aAddress - (long) iCellAddress);
+
+            // This can be a negative number
+            int boxesForDelta = (int) ( delta / KDWordSize );
+
+            // Then work out how many rows and columns would be needed to reach
+            // that value.
+            Point numberOfRowsAndColumns = new Point( 0, 0 );
+
+            if ( delta > 0 )
+            {
+                // Going forwards from current top left of view port.
+                numberOfRowsAndColumns.Y = ( boxesForDelta / rowAndColumnDimensions.Width );
+                numberOfRowsAndColumns.X = ( boxesForDelta - ( numberOfRowsAndColumns.Y * rowAndColumnDimensions.Width ) );
+            }
+            else
+            {
+                numberOfRowsAndColumns.Y = ( boxesForDelta / rowAndColumnDimensions.Width ) - 1;
+                numberOfRowsAndColumns.X = Math.Abs( ( numberOfRowsAndColumns.Y * rowAndColumnDimensions.Width ) - boxesForDelta );
+            }
+
+            // Work our pixel pos
+            ret.X = numberOfRowsAndColumns.X * CellBoxSizeIncludingPadding.Width;
+            ret.Y = numberOfRowsAndColumns.Y * CellBoxSizeIncludingPadding.Height;
+
+            // And take into account padding and header size
+            ret.X += CellBoxRect.X;
+            ret.Y += CellBoxRect.Y;
+
+            //
+            return ret;
+        }
+
+        public Point CoordinateToBox( Point aPixelPos )
+        {
+            Point ret = new Point( -1, -1 );
+            //
+            Size rowsAndColumns = RowsAndColumns;
+            if  ( CellBoxRect.Contains( aPixelPos ) )
+            {
+                Size overallPaddingSize = RowsAndColumnsRemainingPixels;
+
+                // Work out which row this event occurs within.
+                int row = ( aPixelPos.Y - ( overallPaddingSize.Height / 2 ) ) / CellBoxSizeIncludingPadding.Height;
+                if  ( row >= 0 && row < rowsAndColumns.Height )
+                {
+                    // Work out the column index for that cell.
+                    int xpos = aPixelPos.X - ( overallPaddingSize.Width / 2 ) - CellAddressHeaderSize.Width;
+
+                    // How many boxes could we have rendered for that xpos
+                    int boxCount = ( xpos / CellBoxSizeIncludingPadding.Width );
+
+                    ret.X = boxCount;
+                    ret.Y = row;
+                }
+            }
+            //System.Diagnostics.Debug.WriteLine( "aPixelPos[ " + aPixelPos.X + ", " + aPixelPos.Y + " ], boxPos: [ " + ret.X + ", " + ret.Y + " ]" );
+            //
+            return ret;
+        }
+
+        public Point BoxCoordinateByAddress( uint aAddress )
+        {
+            Point ret = new Point( -1, -1 );
+
+            // First, work out how many boxes would be required to draw
+            // the specific cell address
+            uint delta = aAddress - iCellAddress;
+            int boxesForDelta = (int) ( delta / KDWordSize );
+
+            // Then work out how many rows & columns would need to be
+            // drawn to reach that value.
+            Size rowAndColumnDimensions = RowsAndColumns;
+            ret.Y = boxesForDelta / rowAndColumnDimensions.Width;
+            ret.X = boxesForDelta - ( ret.Y * rowAndColumnDimensions.Width );
+            //
+            return ret;
+        }
+
+        public uint AddressByBoxCoordinates( Point aBoxCoordinates )
+        {
+            uint ret = iCellAddress;
+            //
+            if  ( Cells.Count > 0 && aBoxCoordinates.X >= 0 && aBoxCoordinates.Y >= 0 )
+            {
+                Size rowsAndColumns = RowsAndColumns;
+                //
+                ret += (uint) ( aBoxCoordinates.Y * KDWordSize * rowsAndColumns.Width );
+                ret += (uint) ( aBoxCoordinates.X * KDWordSize );
+            }
+            //
+            return ret;
+        }
+
+        public uint AddressByCoordinate( Point aPixelPos )
+        {
+            Point boxPos = CoordinateToBox( aPixelPos );
+            uint ret = AddressByBoxCoordinates( boxPos );
+            return ret;
+        }
+
+        public RawItem RawItemByPixelPos( Point aPixelPos )
+        {
+            RawItem ret = null;
+            //
+            Point boxPos = CoordinateToBox( aPixelPos );
+            HeapCell cell = CellByBoxCoordinates( boxPos );
+            //
+            if ( cell != null )
+            {
+                uint rawItemAddress = AddressByBoxCoordinates( boxPos );
+                HeapCell.TRegion region = cell.RegionForAddress( rawItemAddress );
+                
+                // We only provide raw items for payload sections
+                if ( region == HeapCell.TRegion.EPayload )
+                {
+                    ret = cell[ rawItemAddress ];
+                }
+            }
+            //
+            return ret;
+        }
+
+        public HeapCell CellByPosition( Point aPixelPos )
+        {
+            HeapCell ret = null;
+            //
+            Point boxPos = CoordinateToBox( aPixelPos );
+            if  ( Cells.Count > 0 && boxPos.X >= 0 && boxPos.Y >= 0 )
+            {
+                ret = CellByBoxCoordinates( boxPos );
+            }
+            //
+            return ret;
+        }
+
+        public HeapCell CellByBoxCoordinates( Point aBoxCoordinates )
+        {
+            uint address = AddressByBoxCoordinates( aBoxCoordinates );
+            HeapCell ret = Cells.CellByAddress( address );
+            return ret;
+        }
+
+        public int CellIndex( HeapCell aCell )
+        {
+            int index = -1;
+            Cells.CellByExactAddress( aCell.Address, out index );
+            return index;
+        }
+        #endregion
+
+        #region Sizing
+        internal Rectangle CellBoxRect
+        {
+            get
+            {
+                Size overallPaddingSize = RowsAndColumnsRemainingPixels;
+                Size bodySize = RowsAndColumnsInPixels;
+                //
+                Rectangle ret = new Rectangle();
+                ret.X = ( overallPaddingSize.Width / 2 ) + CellAddressHeaderSize.Width;
+                ret.Y = overallPaddingSize.Height / 2;
+                ret.Width = bodySize.Width;
+                ret.Height = bodySize.Height;
+                //
+                return ret;
+            }
+        }
+
+        [Browsable(false)]
+        internal Size CellBoxSize
+        {
+            get { return iCellBoxSize; }
+            set
+            {
+                iCellBoxSize = value;
+                Invalidate();
+            }
+        }
+
+        internal Size CellBoxSizeIncludingPadding
+        {
+            get
+            {
+                Size size = CellBoxSize;
+                //
+                size.Width += CellPadding.Width;
+                size.Height += CellPadding.Height;
+                //
+                return size;
+            }
+        }
+
+        internal Size CellBoxSizeIncludingPaddingHalved
+        {
+            get
+            {
+                Size size = CellBoxSize;
+                //
+                size.Width += CellPadding.Width;
+                size.Height += CellPadding.Height;
+                //
+                return new Size( size.Width / 2, size.Height / 2 );
+            }
+        }
+
+        [Browsable(false)]
+        internal Size CellPadding
+        {
+            get { return iCellPadding; }
+            set
+            {
+                iCellPadding = value;
+                Invalidate();
+            }
+        }
+
+        internal Size CellPaddingHalved
+        {
+            get { return new Size( iCellPadding.Width / 2, iCellPadding.Height / 2 ); }
+        }
+
+        internal Size RowsAndColumns
+        {
+            get
+            {
+                Size s = this.Size;
+                
+                // First, strip off the address header text width
+                s.Width -= iHeaderTextWidth;
+
+                // Next, calculate how wide each box will be, including padding
+                int boxWidthPerCell = CellBoxSizeIncludingPadding.Width;
+                int cols = Math.Max( 0, ( s.Width - 1 ) / boxWidthPerCell );
+
+                // Now, do the same for rows
+                int boxHeightPerCell = CellBoxSizeIncludingPadding.Height;
+                int rows = Math.Max( 0, ( s.Height - 1 ) / boxHeightPerCell );
+                //
+                return new Size( cols, rows );
+            }
+        }
+
+        internal Size RowsAndColumnsInPixels
+        {
+            get
+            {
+                Size rowsAndCols = RowsAndColumns;
+                Size rowsAndColsInPixels = new Size( rowsAndCols.Width * CellBoxSizeIncludingPadding.Width, rowsAndCols.Height * CellBoxSizeIncludingPadding.Height );
+                //
+                return rowsAndColsInPixels;
+            }
+        }
+
+        internal Size RowsAndColumnsRemainingPixels
+        {
+            get
+            {
+                Size s = this.Size;
+                Size rowsAndColsInPixels = RowsAndColumnsInPixels;
+                //
+                int colsRemainder = s.Width - rowsAndColsInPixels.Width - CellAddressHeaderSize.Width;
+                int rowsRemainder = s.Height - rowsAndColsInPixels.Height;
+                //
+                return new Size( colsRemainder, rowsRemainder );
+            }
+        }
+
+        internal Size RowsAndColumnsRemainingPixelsDistributedEvenly
+        {
+            get
+            {
+                Size overallPaddingSize = RowsAndColumnsRemainingPixels;
+                overallPaddingSize.Width /= 2;
+                overallPaddingSize.Height /= 2;
+                return overallPaddingSize;
+            }
+        }
+
+        internal Size CellAddressHeaderSize
+        {
+            get
+            {
+                return new Size( iHeaderTextWidth, CellBoxSize.Height );
+            }
+        }
+        #endregion
+
+        #region Drawing
+        protected override void OnPaint( PaintEventArgs aArgs )
+        {
+            try
+            {
+                // First must measure the size of the header text as we need
+                // this in order to layout the boxes. We cannot measure the text width
+                // without a graphics object, hence we have to do that here. There must
+                // be a better way?
+                iHeaderTextWidth = iFactory.Renderers.Header.MeasureCellHeaderText( aArgs.Graphics );
+
+                // Store our graphics object for later use
+                iGraphics = aArgs.Graphics;
+
+                // Make the navigator object that will help us lay out the cells
+                HeapRenderingNavigator navigator = new HeapRenderingNavigator( Cells );
+
+                // Queue events we want to receive
+                navigator.iNavBegin += new HeapCtrlLib.Utilities.HeapRenderingNavigator.NavBegin( Navigator_NavBegin );
+                navigator.iNavEnd += new HeapCtrlLib.Utilities.HeapRenderingNavigator.NavEnd( Navigator_NavEnd );
+                navigator.iNavNewRowHeader += new HeapCtrlLib.Utilities.HeapRenderingNavigator.NavNewRowHeader( Navigator_NavNewRowHeader );
+                navigator.iNavNewColumn += new HeapCtrlLib.Utilities.HeapRenderingNavigator.NavNewColumn( Navigator_NavNewColumn );
+                navigator.iNavHeapCellEnd += new HeapCtrlLib.Utilities.HeapRenderingNavigator.NavHeapCellEnd( Navigator_NavHeapCellEnd );
+
+                // The padding amount we apply around the entire control area in order to
+                // center the data.
+                Size centerAlignmentPadding = RowsAndColumnsRemainingPixelsDistributedEvenly;
+
+                // The co-ordinates at which we will render a box (or header).
+                Point startPos = new Point( centerAlignmentPadding.Width, centerAlignmentPadding.Height );
+
+                // Tell the renderers we're about to start
+                iFactory.Renderers.PrepareToNavigate( navigator );
+
+                // Do it
+                navigator.Navigate( iCellIndex, iCellAddress, iHeaderTextWidth, startPos, RowsAndColumns, CellBoxSize, CellPadding );
+
+                // Draw link lines
+                aArgs.Graphics.Clip = new Region( CellBoxRect );
+
+                foreach ( HeapCell cell in BreadcrumbCellsOutgoing )
+                {
+                    RelationshipManager relManager = cell.RelationshipManager;
+
+                    // Draw embedded (outgoing) lines
+                    foreach ( RelationshipInfo outgoingRelationship in relManager.EmbeddedReferencesTo )
+                    {
+                        // Get coordinates of top left corner of the box for the specified 
+                        // address. If we have a clean link, we point right at the cell header.
+                        // If not, then we point at the box in question within the target cell.
+                        uint targetAddress = outgoingRelationship.FromCellRawItem.Data;
+                        if ( outgoingRelationship.IsCleanLink )
+                        {
+                            targetAddress = outgoingRelationship.ToCell.Address;
+                        }
+                        Point pixelPosEnd = AddressToPixelCoordinate( targetAddress );
+                        pixelPosEnd += CellBoxSizeIncludingPaddingHalved;
+                        Point pixelPosStart = AddressToPixelCoordinate( outgoingRelationship.FromCellRawItem.Address );
+                        pixelPosStart += CellBoxSizeIncludingPaddingHalved;
+                        //
+                        Color outgoingPenColour = Color.FromArgb( 120, 255, 0, 0 );
+                        using ( Pen pen = new Pen( outgoingPenColour, 4.0f ) )
+                        {
+                            pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
+                            pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
+                            //
+                            aArgs.Graphics.DrawLine( pen, pixelPosStart, pixelPosEnd );
+                        }
+                    }
+                }
+
+                foreach( HeapCell cell in BreadcrumbCellsIncoming )
+                {
+                    RelationshipManager relManager = cell.RelationshipManager;
+                    
+                    // Draw incoming links
+                    foreach ( HeapCell fromCell in relManager.ReferencedBy )
+                    {
+                        Point pixelPosStart = AddressToPixelCoordinate( fromCell.Address );
+                        pixelPosStart += CellBoxSizeIncludingPaddingHalved;
+                        Point pixelPosEnd = AddressToPixelCoordinate( cell.Address );
+                        pixelPosEnd += CellBoxSizeIncludingPaddingHalved;
+                        //
+                        Color outgoingPenColour = Color.FromArgb( 100, 0, 0, 255 );
+                        using ( Pen pen = new Pen( outgoingPenColour, 4.0f ) )
+                        {
+                            pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
+                            pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
+                            //
+                            aArgs.Graphics.DrawLine( pen, pixelPosStart, pixelPosEnd );
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                iGraphics = null;
+                base.OnPaint( aArgs );
+            }
+        }
+        #endregion
+
+        #region Navigation call backs
+        private void Navigator_NavBegin()
+        {
+            iRenderStartTime = DateTime.Now;
+            //System.Diagnostics.Debug.WriteLine( "DRAW START" );
+
+            // Clear any existing data
+            iGraphics.Clear( Color.White );
+        }
+
+        private void Navigator_NavEnd()
+        {
+            iFactory.Renderers.RenderingComplete( iGraphics );
+
+            System.DateTime renderTimeEnd = DateTime.Now;
+            //System.Diagnostics.Debug.WriteLine( "DRAW END - " + ( renderTimeEnd.Ticks - iRenderStartTime.Ticks ) / 100 );
+        }
+
+        private void Navigator_NavNewRowHeader( uint aAddress, Point aPosition, Size aDimensions, Size aBoxSize, Size aPadding )
+        {
+            // Draw the address at the start of each row
+            iFactory.Renderers.Header.PaintRowHeader( iGraphics, aPosition, CellAddressHeaderSize, aAddress );
+        }
+
+        private void Navigator_NavNewColumn( HeapCell aCell, HeapCellMetaData aMetaData, uint aAddress, Point aPixelPos, Point aBoxPos, Size aDimensions, Size aBoxSize, Size aPadding )
+        {
+            // Draw content
+            iFactory.Renderers.Content.PaintContent( iGraphics, aMetaData, aCell, aAddress, aPixelPos, aBoxSize, aPadding );
+
+            // Draw cell border
+            iFactory.Renderers.ContentBorder.PaintContentBorder( iGraphics, aMetaData, aCell, aAddress, aPixelPos, aBoxSize, aPadding );
+
+            // If we are handling a click & drag operation, then check whether the cell
+            // needs a border.
+            if  ( iMouseSelectionCell != null )
+            {
+                // Get the cell index within the cell array 
+                int index = CellIndex( aCell );
+                int mouseBoundLower = (int) iMouseSelectionCellBoundaryLower.Tag;
+                int mouseBoundUpper = (int) iMouseSelectionCellBoundaryUpper.Tag;
+
+                if  ( index >= mouseBoundLower && index <= mouseBoundUpper )
+                {
+                    iFactory.Renderers.SelectionBorder.PaintSelectionBorder( iGraphics, aMetaData, aCell, aAddress, aPixelPos, aBoxSize, aPadding, THeapSelectionBorderType.ESelectionMouse );
+                }
+            }
+            else if ( iFocusedCellKeyboard == aCell )
+            {
+                // Draw border around currently mouse over'd cell
+                iFactory.Renderers.SelectionBorder.PaintSelectionBorder( iGraphics, aMetaData, aCell, aAddress, aPixelPos, aBoxSize, aPadding, THeapSelectionBorderType.ESelectionKeyboard );
+            }
+            else if ( iFocusedCellMouse == aCell )
+            {
+                // Draw border around currently mouse over'd cell
+                iFactory.Renderers.SelectionBorder.PaintSelectionBorder( iGraphics, aMetaData, aCell, aAddress, aPixelPos, aBoxSize, aPadding, THeapSelectionBorderType.ESelectionMouse );
+            }
+        }
+
+        private void Navigator_NavHeapCellEnd( HeapCell aCell, HeapCellMetaData aMetaData, uint aAddress, Point aPosition, Size aDimensions, Size aBoxSize, Size aPadding )
+        {
+            iFactory.Renderers.HeapCellRenderingComplete( iGraphics, aCell, aMetaData );
+        }
+        #endregion
+
+        #region Internal constants
+        private const uint KDWordSize = SymbianUtils.RawItems.RawItem.KSizeOfOneRawItemInBytes;
+        private Label iLbl_NoContent;
+        #endregion
+
+        #region Internal methods
+        private void InitialiseRenderers()
+        {
+            if  ( Reconstructor != null )
+            {
+                iFactory.Renderers.Initialise( Cells, Reconstructor, this );
+            }
+        }
+
+        private void AsyncShowPopup( HeapCellArrayWithStatistics aCells, Point aPos, RawItem aRawItem )
+        {
+            if ( iFactory.PopupManager.SupportsRawItemInfo && aRawItem != null && aRawItem.Tag != null && aRawItem.Tag is RelationshipInfo && aCells.Count == 1 )
+            {
+                HeapCell cell = aCells[ 0 ];
+                iFactory.PopupManager.PopupShowAsync( cell, aRawItem, Reconstructor.Statistics, aPos, PointToScreen( aPos ), CellBoxSizeIncludingPadding, new KeyEventHandler( HeapDataRenderer_KeyDown ) );
+            }
+            else
+            {
+                iFactory.PopupManager.PopupShowAsync( aCells, Reconstructor.Statistics, aPos, PointToScreen( aPos ), CellBoxSizeIncludingPadding, new KeyEventHandler( HeapDataRenderer_KeyDown ) );
+            }
+
+            iMouseHoverPosition = aPos;
+        }
+        #endregion
+
+        #region Key handling
+        protected override bool ProcessCmdKey(ref Message aMsg, Keys aKeyData)
+        {
+            const int WM_KEYFIRST = 0x100;
+            bool handled = false;
+            //
+            if ( aMsg.Msg == WM_KEYFIRST )
+            {
+                //SymbianUtilsUi.Utilities.WindowMessages.PrintMessage( "RCMD [" + aKeyData + "] ", aMsg.Msg );
+                KeyEventArgs keyArgs = new KeyEventArgs( aKeyData );
+                HandleKey( this, keyArgs );
+                handled = keyArgs.Handled;
+            }
+            //
+            if  ( !handled )
+            {
+                handled = base.ProcessCmdKey( ref aMsg, aKeyData );
+            }
+            //
+            return handled;
+        }
+
+        private void HeapDataRenderer_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
+        {
+            //System.Diagnostics.Debug.WriteLine( "Key Down: " + e.KeyCode );
+            HandleKey( sender, e );
+        }
+
+        private void HandleKey( object sender, System.Windows.Forms.KeyEventArgs e )
+        {
+            switch( e.KeyCode )
+            {
+                    // Move up & down one LINE at a time...
+                case Keys.Down:
+                    ScrollByLineDelta( 1 );
+                    e.Handled = true;
+                    break;
+                case Keys.Up:
+                    ScrollByLineDelta( -1 );
+                    e.Handled = true;
+                    break;
+
+                    // Move up & down one PAGE at a time...
+                case Keys.Next:
+                    ScrollByLineDelta( RowsAndColumns.Height );
+                    e.Handled = true;
+                    break;
+                case Keys.Prior:
+                    ScrollByLineDelta( -RowsAndColumns.Height );
+                    e.Handled = true;
+                    break;
+
+                    // Move to beginning or end of heap
+                case Keys.Home:
+                    ScrollToAddress( Reconstructor.Statistics.HeapAddressStart );
+                    e.Handled = true;
+                    break;
+                case Keys.End:
+                {
+                    uint rowsForHeap = iReconstructor.Statistics.HeapSize / BytesPerRow;
+                    uint rowsPerPage = (uint) RowsAndColumns.Height;
+                    uint targetRow = rowsForHeap - rowsPerPage + 1;
+                    uint address = Reconstructor.Statistics.HeapAddressStart + ( targetRow *  BytesPerRow );
+                    ScrollToAddress( address );
+                    e.Handled = true;
+                    break;
+                }
+
+                    // Move one cell at a time
+                case Keys.Right:
+                {
+                    if  ( iFocusedCellKeyboard != null )
+                    {
+                        int index = Cells.CellIndex( iFocusedCellKeyboard );
+                        if  ( index + 1 < Cells.Count )
+                        {
+                            iFactory.PopupManager.PopupHide();
+                            FocusedCell = Cells[ index + 1 ];
+                            e.Handled = true;
+                        }
+                    }
+                    break;
+                }
+                case Keys.Left:
+                {
+                    if  ( iFocusedCellKeyboard != null )
+                    {
+                        int index = Cells.CellIndex( iFocusedCellKeyboard );
+                        if  ( index - 1 >= 0 )
+                        {
+                            iFactory.PopupManager.PopupHide();
+                            FocusedCell = Cells[ index - 1 ];
+                            e.Handled = true;
+                        }
+                    }
+                    break;
+                }
+
+                    // Unhandled
+                default:
+                    e.Handled = false;
+                    break;
+            }
+        } 
+       
+        private void ScrollByLineDelta( int aLines )
+        {
+            // Update delta to now finally take into account how many
+            // bytes we are going to offset the current address by
+            uint delta = (uint) ( aLines * BytesPerRow );
+
+            // Get current address from renderer
+            uint address = Address;
+
+            // We are going to attempt to offset the current renderer address
+            // by the delta, but we don't want to fall out of bounds (before or
+            // after the min/max address range for the heap).
+            address += delta;
+
+            // Set address
+            ScrollToAddress( address );
+        }
+
+        private void ScrollToAddress( uint aAddress )
+        {
+            // Work out the maximum address. We never prevent the user to scroll so far
+            // that they go past the last line.
+            uint rowsForHeap = Reconstructor.Statistics.HeapSize / BytesPerRow;
+            uint lastRowStartingAddress = Reconstructor.Statistics.HeapAddressStart + ( rowsForHeap *  BytesPerRow );
+            uint address = Math.Min( lastRowStartingAddress, Math.Max( Reconstructor.Statistics.HeapAddressStart, aAddress ) );
+
+            if  ( address >= Reconstructor.Statistics.HeapAddressStart )
+            {
+                Address = address;
+            }
+        }
+        #endregion
+
+        #region Mouse handling
+        /*protected override void WndProc(ref Message m)
+        {
+            SymbianUtilsUi.Utilities.WindowMessages.PrintMessage( "RNDR ", m.Msg );
+            base.WndProc (ref m);
+        }*/
+
+        private void HeapDataRenderer_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
+        {
+            if ( e.Button == MouseButtons.Right )
+            {
+                // Convert the click pos to a cell
+                HeapCell cell = CellByPosition( new Point( e.X, e.Y ) );
+                if ( cell != null && CellRightClicked != null )
+                {
+                    // Convert the click pos to a box
+                    Point boxPos = CoordinateToBox( e.Location );
+                    
+                    // and then convert the box coordinate to an address
+                    uint address = AddressByBoxCoordinates( boxPos );
+                    
+                    // And the try to map that address to a raw item
+                    RawItem rawItem = cell[ address ];
+
+                    // Then notify observer
+                    CellRightClicked( cell, rawItem, e.Location );
+                }
+            }
+            else
+            {
+                bool fc = Focus();
+                Select();
+                //System.Diagnostics.Debug.WriteLine( "RNDR Focused: " + fc );
+
+                if ( Cells.Count > 0 && iMouseSelectionCell == null )
+                {
+                    iMouseSelectionCell = CellByPosition( new Point( e.X, e.Y ) );
+                    //
+                    if ( iMouseSelectionCell != null )
+                    {
+                        iFactory.PopupManager.PopupHide();
+                        //
+                        iMouseSelectionCell.Tag = CellIndex( iMouseSelectionCell );
+                        //
+                        iMouseSelectionCellBoundaryLower = iMouseSelectionCell;
+                        iMouseSelectionCellBoundaryUpper = iMouseSelectionCell;
+                        //
+                        //System.Diagnostics.Debug.WriteLine( "MouseDOWN: [ " + e.X + ", " + e.Y + " ] " + iMouseSelectionCell.ToString() );
+                        //
+                    }
+                }
+            }
+        }
+ 
+        private void HeapDataRenderer_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
+        {
+            //System.Diagnostics.Debug.WriteLine( "MouseUP:   [ " + e.X + ", " + e.Y + " ]" );
+            if  ( Cells.Count > 0 && iMouseSelectionCell != null )
+            {
+                HeapCell mouseUpHeapCell = CellByPosition( new Point( e.X, e.Y ) );
+                //
+                if  ( mouseUpHeapCell != null )
+                {
+                    //System.Diagnostics.Debug.WriteLine( "MouseUP:   [ " + e.X + ", " + e.Y + " ] " + mouseUpHeapCell.ToString() );
+                }
+
+                FocusedCell = mouseUpHeapCell;
+
+                iMouseSelectionCell = null;
+                Invalidate();
+            }
+        }
+
+        private void HeapDataRenderer_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
+        {
+            if  ( Cells.Count > 0 )
+            {
+                Point mousePos = new Point( e.X, e.Y );
+                HeapCell cell = CellByPosition( mousePos );
+                //
+                if  ( cell != null )
+                {
+                    // Also try to get the raw item associated with the hover position. This can
+                    // also return null.
+                    RawItem rawItem = RawItemByPixelPos( mousePos );
+
+                    // Now process the mouse movement
+                    if  ( iMouseSelectionCell != null )
+                    {
+                        int mouseBoundStart = (int) iMouseSelectionCell.Tag;
+                        int mouseBoundLower = (int) iMouseSelectionCellBoundaryLower.Tag;
+                        int mouseBoundUpper = (int) iMouseSelectionCellBoundaryUpper.Tag;
+                        int originalSelectionRangeCount = ( mouseBoundUpper - mouseBoundLower );
+
+                        int index = CellIndex( cell );
+                        if  ( index < mouseBoundStart )
+                        {
+                            // Reset upper boundary
+                            iMouseSelectionCellBoundaryUpper = iMouseSelectionCell;
+                            iMouseSelectionCellBoundaryLower = cell;
+                            iMouseSelectionCellBoundaryLower.Tag = index;
+                        }
+                        else if ( index > mouseBoundStart )
+                        {
+                            // Reset lower boundary
+                            iMouseSelectionCellBoundaryLower = iMouseSelectionCell;
+                            iMouseSelectionCellBoundaryUpper = cell;
+                            iMouseSelectionCellBoundaryUpper.Tag = index;
+                        }
+                        else if ( index == mouseBoundStart )
+                        {
+                            iMouseSelectionCellBoundaryUpper = iMouseSelectionCell;
+                            iMouseSelectionCellBoundaryLower = iMouseSelectionCell;
+                        }
+
+                        // Update boundary
+                        mouseBoundLower = (int) iMouseSelectionCellBoundaryLower.Tag;
+                        mouseBoundUpper = (int) iMouseSelectionCellBoundaryUpper.Tag;
+
+                        // Work out if this is a multi-select scenario
+                        int selectionRangeCount = mouseBoundUpper - mouseBoundLower;
+                        bool selectionHasChanged = ( selectionRangeCount != originalSelectionRangeCount );
+                        if  ( selectionHasChanged && Math.Abs( selectionRangeCount ) + 1 > 1 )
+                        {
+                            // Build array of selected cells
+                            HeapCellArrayWithStatistics cells = new HeapCellArrayWithStatistics();
+                            for( index = mouseBoundLower; index <= mouseBoundUpper; index++ )
+                            {
+                                cell = Cells[ index ];
+                                cells.Add( cell );
+                            }
+
+                            // Show popup
+                            bool popupVisible = iFactory.PopupManager.Visible;
+                            Point pos = new Point( e.X, e.Y );
+
+                            if  ( !popupVisible || !( e.X == iMouseHoverPosition.X && e.Y == iMouseHoverPosition.Y ) )
+                            {
+                                if  ( !popupVisible )
+                                {
+                                    //System.Diagnostics.Debug.WriteLine( "Mouse MOVE - MS [popup not vis], Differing Coords: [ " + e.X + ", " + e.Y + " ] -> Popup Show" );
+                                    AsyncShowPopup( cells, pos, rawItem );
+                                }
+                                else
+                                {
+                                    //System.Diagnostics.Debug.WriteLine( "Mouse MOVE - MS [popup visible], Differing Coords: [ " + e.X + ", " + e.Y + " ] -> Popup Already Shown" );
+                                    iMouseHoverPosition = pos;
+                                    iFactory.PopupManager.PopupRelocate( cells, Reconstructor.Statistics, pos, PointToScreen( pos ), CellBoxSizeIncludingPadding );
+                                }
+                            }
+                            else if ( !popupVisible )
+                            {
+                                //System.Diagnostics.Debug.WriteLine( "Mouse MOVE - MS [popup not vis], Differing Coords: [ " + e.X + ", " + e.Y + " ] -> Popup Show" );
+                                AsyncShowPopup( cells, pos, rawItem );
+                            }
+                       }
+                        else if ( selectionHasChanged )
+                        {
+                            //System.Diagnostics.Debug.WriteLine( "Mouse MOVE - Have Existing Selection: [ " + e.X + ", " + e.Y + " ] -> Single Item Hover" );
+                            iFactory.PopupManager.PopupHide();
+                        }
+                    }
+                    else
+                    {
+                        if  ( iFactory.PopupManager.Visible )
+                        {
+                            if  ( ! ( e.X == iMouseHoverPosition.X && e.Y == iMouseHoverPosition.Y ) )
+                            {
+                                //System.Diagnostics.Debug.WriteLine( "Mouse MOVE: [ " + e.X + ", " + e.Y + " ] -> Popup Hidden" );
+                                iFactory.PopupManager.PopupHide();
+                            }
+                        }
+                        else if  ( ! ( e.X == iMouseHoverPosition.X && e.Y == iMouseHoverPosition.Y ) )
+                        {
+                            //System.Diagnostics.Debug.WriteLine( "Mouse MOVE: [ " + e.X + ", " + e.Y + " ] -> Popup Show Async" );
+                            Point pos = new Point( e.X, e.Y );
+                            HeapCellArrayWithStatistics cells = new HeapCellArrayWithStatistics();
+                            cells.Add( cell );
+                            AsyncShowPopup( cells, pos, rawItem );
+                        }
+                    }
+                    //
+                    Invalidate();
+                }
+                //
+                iFocusedCellMouse = cell;
+            }
+        }
+
+        private void HeapDataRenderer_MouseLeave(object sender, System.EventArgs e)
+        {
+            Point pos = System.Windows.Forms.Cursor.Position;
+            //System.Diagnostics.Debug.WriteLine( "Mouse LEAVE: Popup hidden - pos: " + pos + ", locY: " + PointToScreen( Location ).Y );
+
+            iFactory.PopupManager.PopupHide();
+            //
+            iFocusedCellMouse = null;
+            iMouseSelectionCell = null;
+            //
+            Invalidate();
+        }
+
+        private void HeapDataRenderer_MouseWheel(object sender, MouseEventArgs e)
+        {
+            // For each scroll of the mouse wheel
+            int mouseScrollLines = SystemInformation.MouseWheelScrollLines;
+
+            // Odd(?), but scrolling down results in a negative delta (-120), and
+            // scrolling up results in positive
+            int delta = ( e.Delta < 0 ) ? 1 : -1;
+            delta *= mouseScrollLines;
+
+            ScrollByLineDelta( delta );
+        }
+
+        private void HeapDataRenderer_MouseDoubleClick( object sender, MouseEventArgs e )
+        {
+            HeapCell cell = CellByPosition( new Point( e.X, e.Y ) );
+            //
+            if ( cell !=  null && CellDoubleClicked != null )
+            {
+                CellDoubleClicked( cell );
+            }
+        }
+        #endregion
+
+        #region Data members
+        private HeapCell iFocusedCellKeyboard = null;
+        private HeapCell iFocusedCellMouse = null;
+        private Point iMouseHoverPosition;
+        private HeapCell iMouseSelectionCell = null;
+        private HeapCell iMouseSelectionCellBoundaryLower = null;
+        private HeapCell iMouseSelectionCellBoundaryUpper = null;
+        private HeapReconstructor iReconstructor = null;
+        private HeapCellArray iCells = new HeapCellArray();
+        private int iHeaderTextWidth = 30;
+        private Size iCellBoxSize = new Size( 100, 100 );
+        private Size iCellPadding = new Size(  20,  20 );
+        private int iCellIndex = 0;
+        private uint iCellAddress = 0;
+        private Factory iFactory = null;
+        private Graphics iGraphics = null;
+        private THeapCtrlZoom iZoom = THeapCtrlZoom.EHeapCtrlZoomMedium;
+        private HeapCellArrayWithStatistics iSelectedCells = new HeapCellArrayWithStatistics();
+        private System.DateTime iRenderStartTime = new DateTime();
+        private HeapCellArrayUnsorted iBreadcrumbCellsOutgoing = new HeapCellArrayUnsorted();
+        private HeapCellArrayUnsorted iBreadcrumbCellsIncoming = new HeapCellArrayUnsorted();
+        #endregion
+    }
+}