sysperfana/heapanalyser/Libraries/UI/HeapCtrlLib/Renderers/HeapDataRenderer.cs
author Matti Laitinen <matti.t.laitinen@nokia.com>
Tue, 15 Jun 2010 12:47:20 +0300
changeset 8 15296fd0af4a
permissions -rw-r--r--
HeapAnalyser 1.1.0

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