/// <summary>
        /// Handles the movement of the mouse in the row header.
        /// </summary>
        /// <param name="e">The mouse movement event arguments.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // This gets the location of the mouse in document coordinates.
            Point mouseLocation = e.GetPosition(this);

            // The anchorRow describes the starting row for an extended selection.  The selectedRow is the one over which
            // the mouse is currently.
            ReportRow selectedRow = this.ReportGrid.Rows.FindRowAt(mouseLocation.Y);
            ReportRow anchorRow   = this.ReportGrid.Rows.FindRowAt(this.anchorPoint.Y);

            // The action taken by a mouse movement in the row heading of a viewer is driven by a set of states.  These states
            // are driven, in turn, by where the mouse started and what buttons are pressed.  They can instruct the viewer to
            // resize the rows, move the row, delete the row or sort the row.
            switch (this.mouseState)
            {
            case MouseState.Selecting:

                // When dragging the mouse around the row headings, the most recent range will be replaced.  The new range consist
                // of every row between the anchor row and the row over which the mouse is currently.

                // HACK - Fix this when the selected range is guaranteed to have rows in it.
                if (this.selectedRanges.Count > 0)
                {
                    List <ReportRow> lastRange = this.selectedRanges[this.selectedRanges.Count - 1];
                    if ((lastRange[lastRange.Count - 1] != selectedRow) && (selectedRow != null))
                    {
                        // This will select each row between the last selected row and the one just selected.  Note that the
                        // list of rows is distinct, so duplicate rows are ignored.
                        ReportRow firstRow  = anchorRow.Top < selectedRow.Top ? anchorRow : selectedRow;
                        ReportRow secondRow = anchorRow.Top < selectedRow.Top ? selectedRow : anchorRow;
                        lastRange.Clear();
                        foreach (ReportRow reportRow in this.ReportGrid.Rows)
                        {
                            if (firstRow.Top <= reportRow.Top && reportRow.Top <= secondRow.Top)
                            {
                                lastRange.Add(reportRow);
                            }
                        }

                        // Add the row Header to the range also so the it will be highlight too.
                        lastRange.Add(ReportGrid.reportRowCollection.FindRowAt(0));

                        // This will select all the cells in the range of rows that have been selected.  Everything else will be
                        // cleared.  The active cell -- the one that has the input focus -- is the first cell of the anchor row.
                        SelectRows();
                    }
                }

                break;

            case MouseState.ResizingRow:

                // This will calculate the current row width based on the difference between the anchor point and the current mouse
                // location.
                double rowHeight = selectedRow.Height + (mouseLocation.Y - this.anchorPoint.Y);

                // The owner of the header canvas is called to draw the row boundary line so that it follows the mouse when the
                // user is resizing the rows.  This operation can't be done from this canvas because the size of the scrolling
                // canvas -- where the row boundary cursor line is displayed -- isn't known here.
                if (this.ResizeMouseMove != null)
                {
                    this.ResizeMouseMove(this, new ResizeRowEventArgs(selectedRow, rowHeight, false));
                }

                // As the size of the row is dragged around, the Popup widow displays a human readable form of the width.
                this.rowHeightPopup.Content = rowHeight;

                break;

            case MouseState.ButtonDown:

                // When the user presses the top mouse button, they initiate some drag operation and the mouse activity is
                // captured by the row header window.  If the user is simply moving the mouse over the window, then feedback is
                // given in the shape of the cursor. This formula determins if the mouse has moved an absolute distance of four
                // pixels from the original location. If it has, the user has selected a movement operation for the row.
                // Otherwise, the mouse operation will be interpreted as a request for a new sort order when the top mouse button
                // is lifted.
                if (Math.Sqrt(Math.Pow(mouseLocation.Y - this.mouseDownLocation.Y, 2.0) +
                              Math.Pow(mouseLocation.Y - this.mouseDownLocation.Y, 2.0)) > DynamicReport.headerDragTrigger)
                {
                    // At this point the mouse movements are intepreted as drag-and-drop operations for the row headers. The
                    // drop states determines what happens when the mouse button is released.  It can either be moved, deleted or
                    // have no action taken.
                    this.mouseState       = MouseState.DraggingRow;
                    this.destinationState = DropAction.NoAction;

                    // When dragging a row, the proposed destination appears as a set of two red arrows marking where the row
                    // will reside if dropped.  The scale of the destination arrows must match the scale of the report.
                    this.destinationPopup.Scale      = this.ReportGrid.DynamicReport.Scale;
                    this.destinationPopup.Visibility = Visibility.Hidden;
                    this.destinationPopup.IsOpen     = true;

                    // This sets up the dragging operation by creating a destination cursor (the red arrows that point to where the
                    // row will 'snap' into place), a row cursor (it looks like the row header was ripped out of the page)
                    // and positions the row cursor at the tip of the current mouse location.
                    this.headerPopup.Resources = this.ReportGrid.Resources;
                    this.headerPopup.Content   = this.headerCells;
                    this.headerPopup.Scale     = this.ReportGrid.DynamicReport.Scale;
                    this.headerPopup.Height    = selectedRow.Height;
                    this.headerPopup.Width     = this.ActualWidth;
                    this.headerPopup.Location  = mouseLocation;
                    this.headerPopup.IsOpen    = true;
                }

                break;

            case MouseState.DraggingRow:

                // If the window that contains the row headings contains the cursor, then it's possible that a destination is
                // selected for the row drag-and-drop operation.  If the cursor is outside of the header quadrant, the row
                // will be deleted when the mouse button is released.
                if (this.Viewport.Contains(mouseLocation))
                {
                    // Any operation inside the visible header gets the basic pointing arrow for a cursor.
                    this.Cursor = this.selectRow;

                    // When the mouse is inside the header quadrant but there is no destination selected, then nothing will happen
                    // when the mouse button is release.
                    this.destinationState = DropAction.NoAction;

                    // This attempts to find a destination for the row operation.
                    if (selectedRow != null)
                    {
                        // A row can't be its own destination.
                        if (anchorRow != selectedRow)
                        {
                            // A destination is selected if the left edge of the target column is entirely visible in the header
                            // quadrant and the left half of the column header contains the current mouse location.
                            Rect testAreaTop = new Rect(this.Viewport.Left, selectedRow.Top, this.Viewport.Width,
                                                        selectedRow.Height / 2.0);
                            if (testAreaTop.Contains(mouseLocation) && anchorRow.Bottom != selectedRow.Top)
                            {
                                this.destinationState = DropAction.Select;
                                this.destinationRow   = selectedRow;
                            }

                            // This will test the right half of each of the colum headers.  If the cursor is over the right half
                            // and the rightmost part of the destination is entirely visible in the header, then it can be a
                            // destination.
                            Rect testAreaBottom = new Rect(this.Viewport.Left, selectedRow.Top + selectedRow.Height / 2.0,
                                                           this.Viewport.Width, selectedRow.Height / 2.0);
                            if (testAreaBottom.Contains(mouseLocation) && selectedRow.Bottom != anchorRow.Top)
                            {
                                this.destinationState = DropAction.Select;
                                this.destinationRow   = null;
                                foreach (ReportRow nextRow in this.ReportGrid.Rows)
                                {
                                    if (nextRow.Top == selectedRow.Bottom)
                                    {
                                        this.destinationRow = nextRow;
                                    }
                                }
                            }
                        }
                    }

                    // If a valid destination was found in the search above, move the set of red arrows (the destination cursor)
                    // over the exact spot where the row will be moved.
                    if (this.destinationState == DropAction.Select)
                    {
                        this.destinationPopup.VerticalOffset = this.destinationRow == null ?
                                                               this.ReportGrid.ExtentHeight : this.destinationRow.Top;
                        this.destinationPopup.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        this.destinationPopup.Visibility = Visibility.Hidden;
                    }
                }
                else
                {
                    // If the mouse isn't over the row header quadrant, a big 'X' instead of the destination cursor give the
                    // user feedback that the row will be dropped from the viewer if they release the mouse button.
                    this.destinationPopup.Visibility = Visibility.Hidden;
                    this.Cursor = this.bigEx;

                    // This will instruct the 'mouse up' action to delete the currently selected row.
                    this.destinationState = DropAction.Delete;
                }

                // The cursor row is really a floating window, not a cursor.  It needs to be moved to match the location of the
                // mouse.  Note that the floating window doesn't have a parent, so the coordinates are in screen units.
                this.headerPopup.Location = mouseLocation;

                break;

            case MouseState.ButtonUp:

                // This will determine which cursor should be used when the button isn't pressed while moving the mouse: a horizontal
                // size cursor or a regular arrow cursor.  If the mouse is over the bottom or top edge of the row, then the
                // horizontal resizing cursor is used.
                bool isResizingRow = false;

                // This will attempt to hit test the current row to see if it is a candidate for resizing.
                if (selectedRow != null)
                {
                    // This is a 'Hit Test' for the bottom edge of the row header tile to see if the user is trying to change the
                    // size of the row.
                    if (selectedRow.Top + selectedRow.Height - DynamicReport.splitBorder <= mouseLocation.Y &&
                        mouseLocation.Y < selectedRow.Top + selectedRow.Height)
                    {
                        isResizingRow = true;
                    }

                    // This is a 'Hit Test' for the top edge of the row header tile to see if the user is trying to change the
                    // size of the row.
                    if (selectedRow.Top <= mouseLocation.Y &&
                        mouseLocation.Y < selectedRow.Top + DynamicReport.splitBorder)
                    {
                        isResizingRow = true;
                    }
                }

                // Only the selection cursor is used when the header is frozen.  When unfrozen, select the resizing cursor when
                // the mouse is over the edge of the column header.
                this.Cursor = this.IsHeaderFrozen || !isResizingRow ? this.selectRow : this.horizontalSplit;

                break;
            }
        }
        /// <summary>
        /// Handles the mouse button being released over a row header.
        /// </summary>
        /// <param name="state">The thread initialization parameter.</param>
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            // This gets the location of the mouse in document coordinates.
            Point mouseLocation = e.GetPosition(this);

            // Evaluate the state of the keyboard.  Key combinations involving the shift and control keys will alter the areas that
            // are selected.
            bool isShiftKeyPressed   = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
            bool isControlKeyPressed = ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control);

            // The target row for any movement operations is the last row selected.
            ReportRow anchorRow = this.ReportGrid.Rows.FindRowAt(this.anchorPoint.Y);

            // The mouse state indications what action should be taken when the mouse button is released.
            switch (this.mouseState)
            {
            case MouseState.ResizingRow:

                // At the conclusion of the resizing operation a decision is made to remove or change the width of a row.  If
                // the size is reduced to zero (or less), this gesture is taken as a command to remove the row.  Any positive
                // value for a width will result in a row change command.
                double rowHeight = anchorRow.Height + (mouseLocation.Y - this.anchorPoint.Y);
                rowHeight = rowHeight < 0.0 ? 0.0 : rowHeight;
                if (this.ResizeMouseMove != null)
                {
                    this.ResizeMouseMove(this, new ResizeRowEventArgs(anchorRow, rowHeight, true));
                }

                // Note - this is where the logic should go for removing or resizing a row.  It should probably bubble up through
                // the Report and be handled by some higher logic that determins if removing or resizing a row is a legal action.
                // Currently it is not supported.

                // This will hide the popup until it is needed again.
                this.rowHeightPopup.IsOpen = false;

                // This is a momentary button: the column heading will loose the selection state when the button is released.
                foreach (ReportCell reportCell in this.headerCells)
                {
                    reportCell.IsSelected = false;
                }

                break;

            case MouseState.DraggingRow:

                // The action taken when the dragging operation is complete depends on whether a valid destination is selected or
                // whether the row is meant to be deleted.
                switch (this.destinationState)
                {
                case DropAction.Select:

                    // This will move the row from its current location to the desired location.
                    if (this.destinationPopup.Visibility == Visibility.Visible)
                    {
                        // TODO - This should generate an event to move a row from one place to another.
                    }

                    break;

                case DropAction.Delete:

                    // This will delete the row from the view.
                    this.ReportGrid.Rows.Remove(anchorRow);

                    break;
                }

                // This is a momentary button: the column heading will loose the selection state when the button is released.
                foreach (ReportCell reportCell in this.headerCells)
                {
                    reportCell.IsSelected = false;
                }

                break;
            }

            // This resets the state of the mouse for the next operation.
            this.mouseState = MouseState.ButtonUp;

            if (this.headerPopup.IsOpen)
            {
                this.headerPopup.IsOpen = false;
            }

            // Hide the destination cursor when the mouse is released.
            if (this.destinationPopup.IsOpen)
            {
                this.destinationPopup.IsOpen = false;
            }

            // Release the Hounds, errr... mouse.
            Mouse.Capture(null);
        }
        /// <summary>
        /// Handles the mouse button being pressed.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            // The sort order is maintained in this structure.
            this.sortOrder = new List <SortItem>();

            // This gets the location of the mouse in document coordinates.
            Mouse.Capture(this);

            // This state variable will control how the 'Mouse Move' and 'Mouse Up' event handlers interpret the user action.  The
            // 'selectedRow' field is used as the starting point for any drag-and-drop type of operation.
            this.mouseState = MouseState.ButtonDown;

            // Evaluate the state of the keyboard.  Key combinations involving the shift and control keys will alter the areas that
            // are selected.
            bool isShiftKeyPressed   = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
            bool isControlKeyPressed = ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control);

            // This will use the current position for an anchor unless the shift key is pressed.
            this.mouseDownLocation = e.GetPosition(this);
            if (!isShiftKeyPressed)
            {
                this.anchorPoint = this.mouseDownLocation;
            }

            // The mouse indicates which column has been selected and the anchor indicates the starting point of the selection in
            // an extended selection operation.
            ReportRow selectedRow = this.ReportGrid.Rows.FindRowAt(this.mouseDownLocation.Y);
            ReportRow anchorRow   = this.ReportGrid.Rows.FindRowAt(this.anchorPoint.Y);

            Point mouseDownLocation = e.GetPosition(this);

            if (!isShiftKeyPressed)
            {
                IInputElement    iInputElement    = this.InputHitTest(mouseDownLocation);
                DependencyObject dependencyObject = iInputElement as DependencyObject;
                while (dependencyObject != null)
                {
                    if (DynamicReport.GetCell(dependencyObject) != null)
                    {
                        Keyboard.Focus(dependencyObject as IInputElement);
                        break;
                    }
                    dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
                }
            }

            // Every cell that appears in the header canvas is considered part of the selectable header.  This will collect all the
            // selected cells in a list while creating a rectangle that is the union of all those selected cells.
            this.headerCells.Clear();
            foreach (ReportColumn reportColumn in this.ReportGrid.Columns)
            {
                ReportCell reportCell = selectedRow[reportColumn];
                if (reportColumn.Left < this.ActualWidth)
                {
                    this.headerCells.Add(reportCell);
                }
            }

            // If a row is selected then the position and movement of the mouse will suggest one of several gestures that need
            // to be interpreted: is a row being moved, is it being resized, is it being deleted or selected?  The code below
            // will begin to interpret the input gesture.
            if (selectedRow != null)
            {
                // The header has two modes: when the headers are frozen, only selection operations are enabled with the mouse.
                // When not frozen, the rows can be moved, resized, resorted and removed.
                if (this.IsHeaderFrozen)
                {
                    // The shift and control key extend the selection operation in the same way as Microsoft Excel.
                    if (isShiftKeyPressed || isControlKeyPressed)
                    {
                        // When the shift key is pressed during row selection, every row between the last row selected
                        // and the current row is selected.
                        if (isShiftKeyPressed)
                        {
                            // In the unlikely event that the shift key was down during the setting of the anchor point, this will
                            // create a dummy entry in the list of selected row ranges.
                            if (this.selectedRanges.Count == 0)
                            {
                                List <ReportRow> reportRows = new List <ReportRow>();
                                reportRows.Add(selectedRow);
                                this.selectedRanges.Add(reportRows);
                            }

                            // The most recent range will be replaced with a new range when the mouse is dragged around the row
                            // headers.  This has the effect of clearing the rows that are no longer selected and selecting only
                            // the rows between the anchor and the currently selected row.
                            List <ReportRow> lastRange = this.selectedRanges[this.selectedRanges.Count - 1];

                            // This will select each row between the last selected row and the one just selected.  Note that the
                            // list of rows is distinct, so duplicate rows are ignored.
                            ReportRow firstRow  = anchorRow.Top < selectedRow.Top ? anchorRow : selectedRow;
                            ReportRow secondRow = anchorRow.Top < selectedRow.Top ? selectedRow : anchorRow;
                            lastRange.Clear();
                            foreach (ReportRow reportRow in this.ReportGrid.Rows)
                            {
                                if (firstRow.Top <= reportRow.Top && reportRow.Top <= secondRow.Top)
                                {
                                    lastRange.Add(reportRow);
                                }
                            }
                        }

                        // When the control key is pressed a single row is added to the range of rows selected.
                        if (isControlKeyPressed)
                        {
                            // This removes any previous instance of this row in the selection.
                            foreach (List <ReportRow> rowRange in this.selectedRanges)
                            {
                                if (rowRange.Contains(selectedRow))
                                {
                                    rowRange.Remove(selectedRow);
                                }
                            }

                            // The row is added (or re-added) at the start of the range of selected rows.
                            List <ReportRow> reportRows = new List <ReportRow>();
                            reportRows.Add(selectedRow);
                            this.selectedRanges.Add(reportRows);
                        }
                    }
                    else
                    {
                        // The row is added at the start of a new range of selected rows.
                        List <ReportRow> reportRows = new List <ReportRow>();

                        // The if condition below fixes issue that allows the right click to not clear the selected rows.
                        // Hence it allows every other condition to clear the state.
                        if (e.RightButton != MouseButtonState.Pressed)
                        {
                            // A simple selection that doesn't involve the modifier keys will clear out any previously selected ranges.
                            this.selectedRanges.Clear();
                        }
                        else
                        {
                            // This removes any previous instance of this row in the selection.
                            foreach (List <ReportRow> rowRange in this.selectedRanges)
                            {
                                if (rowRange.Contains(selectedRow))
                                {
                                    rowRange.Remove(selectedRow);
                                }
                            }
                        }

                        reportRows.Add(selectedRow);
                        this.selectedRanges.Add(reportRows);
                    }

                    // This will select all the rows in the selected ranges of rows and remove the selection from all the
                    // rest of the cells.
                    SelectRows();

                    // This instructs the event handlers how the mouse movement is to be interpreted.
                    this.mouseState = MouseState.Selecting;
                }
                else
                {
                    // The top mouse button can either select the row or begin a resizing operation.  This will perform a 'Hit
                    // Test' to see which operation should be performed.
                    if (e.LeftButton == MouseButtonState.Pressed)
                    {
                        // This is a 'Hit Test' for the bottom edge of the row header tile to see if the user is trying to change
                        // the size of the row.  If the mouse is close to the bottom edge, then the drag operation to change the
                        // size of the tile is begun.
                        if (selectedRow.Bottom - DynamicReport.splitBorder <= this.mouseDownLocation.Y &&
                            this.mouseDownLocation.Y < selectedRow.Bottom)
                        {
                            this.resizeStart    = selectedRow.Bottom;
                            this.mouseState     = MouseState.ResizingRow;
                            this.destinationRow = null;
                        }
                        else
                        {
                            // This is a 'Hit Test' for the top edge of the row header tile to see if the user is trying to change
                            // the size of the row.  Note that because the top edge really belongs to the previous row header when
                            // resizing, that the previous row is selected for the operation.
                            if (selectedRow.Top <= this.mouseDownLocation.Y &&
                                this.mouseDownLocation.Y < selectedRow.Top + DynamicReport.splitBorder)
                            {
                                this.resizeStart = selectedRow.Top;
                                this.mouseState  = MouseState.ResizingRow;
                                foreach (ReportRow reportRow in this.ReportGrid.Rows)
                                {
                                    if (reportRow.Bottom == selectedRow.Top)
                                    {
                                        selectedRow = reportRow;
                                    }
                                }
                                this.destinationRow = null;
                            }
                        }
                    }

                    // At this point, a resizing operation has been selected from the input gesture of the mouse.
                    if (this.mouseState == MouseState.ResizingRow)
                    {
                        // The parent window will watch for this event to tell it how to draw the row width indicator lines. The
                        // dimension and location of those lines are outside of this window and must be handled by the parent.
                        if (this.ResizeMouseMove != null)
                        {
                            this.ResizeMouseMove(this, new ResizeRowEventArgs(selectedRow, selectedRow.Height,
                                                                              false));
                        }

                        // This window provides quantitative feedback for the new width of the row.  The offsets were arrived at
                        // empirically from reverse engineering Excel.
                        this.rowHeightPopup.VerticalOffset = this.mouseDownLocation.Y - 2.0;
                        this.rowHeightPopup.Content        = selectedRow.Height;
                        this.rowHeightPopup.IsOpen         = true;
                    }
                    else
                    {
                        // This will select the button momentarily for drag-and-drop and sorting operations.
                        foreach (ReportCell reportCell in this.headerCells)
                        {
                            reportCell.IsSelected = true;
                        }
                    }
                }
            }
        }