/*************************************************************************************

   Extended WPF Toolkit

   Copyright (C) 2007-2013 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license 

   For more features, controls, and fast professional support,
   pick up the Plus Edition at http://xceed.com/wpf_toolkit

   Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids

  ***********************************************************************************/

using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Security;
using System.Security.Permissions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Xceed.Wpf.Toolkit.Core.Input;
using Xceed.Wpf.Toolkit.Core;
using Xceed.Wpf.Toolkit.Core.Utilities;

namespace Xceed.Wpf.Toolkit.Zoombox
{
  [TemplatePart( Name = PART_VerticalScrollBar, Type = typeof( ScrollBar ) )]
  [TemplatePart( Name = PART_HorizontalScrollBar, Type = typeof( ScrollBar ) )]
  public sealed class Zoombox : ContentControl
  {
    private const string PART_VerticalScrollBar = "PART_VerticalScrollBar";
    private const string PART_HorizontalScrollBar = "PART_HorizontalScrollBar";

    #region Constructors

    static Zoombox()
    {
      Zoombox.DefaultStyleKeyProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( typeof( Zoombox ) ) );
      Zoombox.ClipToBoundsProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( true ) );
      Zoombox.FocusableProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( true ) );
      Zoombox.HorizontalContentAlignmentProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( HorizontalAlignment.Center, new PropertyChangedCallback( Zoombox.RefocusView ) ) );
      Zoombox.VerticalContentAlignmentProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( VerticalAlignment.Center, new PropertyChangedCallback( Zoombox.RefocusView ) ) );
      Zoombox.ContentProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( ( PropertyChangedCallback )null, new CoerceValueCallback( Zoombox.CoerceContentValue ) ) );
    }

    public Zoombox()
      : base()
    {
      try
      {
        new UIPermission( PermissionState.Unrestricted ).Demand();
        _cacheBits[ ( int )CacheBits.HasUIPermission ] = true;
      }
      catch( SecurityException )
      {
      }

      this.InitCommands();

      // use the LayoutUpdated event to keep the Viewport in sync
      this.LayoutUpdated += new EventHandler( this.OnLayoutUpdated );
      this.AddHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnSizeChanged ), true );

      this.CoerceValue( Zoombox.ViewStackModeProperty );

      this.Loaded += this.Zoombox_Loaded;
    }

    #endregion

    #region AnimationAccelerationRatio Property

    public static readonly DependencyProperty AnimationAccelerationRatioProperty =
      DependencyProperty.Register( "AnimationAccelerationRatio", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( 0d ), 
          new ValidateValueCallback( Zoombox.ValidateAccelerationRatio ) );

    public double AnimationAccelerationRatio
    {
      get
      {
        return ( double )this.GetValue( Zoombox.AnimationAccelerationRatioProperty );
      }
      set
      {
        this.SetValue( Zoombox.AnimationAccelerationRatioProperty, value );
      }
    }

    private static bool ValidateAccelerationRatio( object value )
    {
      double newValue = ( double )value;
      if( newValue < 0 || newValue > 1 || DoubleHelper.IsNaN( newValue ) )
        throw new ArgumentException( ErrorMessages.GetMessage( "AnimationAccelerationRatioOOR" ) );

      return true;
    }

    #endregion

    #region AnimationDecelerationRatio Property

    public static readonly DependencyProperty AnimationDecelerationRatioProperty =
      DependencyProperty.Register( "AnimationDecelerationRatio", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( 0d ), 
          new ValidateValueCallback( Zoombox.ValidateDecelerationRatio ) );

    public double AnimationDecelerationRatio
    {
      get
      {
        return ( double )this.GetValue( Zoombox.AnimationDecelerationRatioProperty );
      }
      set
      {
        this.SetValue( Zoombox.AnimationDecelerationRatioProperty, value );
      }
    }

    private static bool ValidateDecelerationRatio( object value )
    {
      double newValue = ( double )value;
      if( newValue < 0 || newValue > 1 || DoubleHelper.IsNaN( newValue ) )
        throw new ArgumentException( ErrorMessages.GetMessage( "AnimationDecelerationRatioOOR" ) );

      return true;
    }

    #endregion

    #region AnimationDuration Property

    public static readonly DependencyProperty AnimationDurationProperty =
      DependencyProperty.Register( "AnimationDuration", typeof( Duration ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( new Duration( TimeSpan.FromMilliseconds( 300 ) ) ) );

    public Duration AnimationDuration
    {
      get
      {
        return ( Duration )this.GetValue( Zoombox.AnimationDurationProperty );
      }
      set
      {
        this.SetValue( Zoombox.AnimationDurationProperty, value );
      }
    }

    #endregion

    #region AreDragModifiersActive Property

    private static readonly DependencyPropertyKey AreDragModifiersActivePropertyKey =
      DependencyProperty.RegisterReadOnly( "AreDragModifiersActive", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty AreDragModifiersActiveProperty = Zoombox.AreDragModifiersActivePropertyKey.DependencyProperty;

    public bool AreDragModifiersActive
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.AreDragModifiersActiveProperty );
      }
    }

    private void SetAreDragModifiersActive( bool value )
    {
      this.SetValue( Zoombox.AreDragModifiersActivePropertyKey, value );
    }

    #endregion

    #region AreRelativeZoomModifiersActive Property

    private static readonly DependencyPropertyKey AreRelativeZoomModifiersActivePropertyKey =
      DependencyProperty.RegisterReadOnly( "AreRelativeZoomModifiersActive", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty AreRelativeZoomModifiersActiveProperty = Zoombox.AreRelativeZoomModifiersActivePropertyKey.DependencyProperty;

    public bool AreRelativeZoomModifiersActive
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.AreRelativeZoomModifiersActiveProperty );
      }
    }

    private void SetAreRelativeZoomModifiersActive( bool value )
    {
      this.SetValue( Zoombox.AreRelativeZoomModifiersActivePropertyKey, value );
    }

    #endregion

    #region AreZoomModifiersActive Property

    private static readonly DependencyPropertyKey AreZoomModifiersActivePropertyKey =
      DependencyProperty.RegisterReadOnly( "AreZoomModifiersActive", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty AreZoomModifiersActiveProperty = Zoombox.AreZoomModifiersActivePropertyKey.DependencyProperty;

    public bool AreZoomModifiersActive
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.AreZoomModifiersActiveProperty );
      }
    }

    private void SetAreZoomModifiersActive( bool value )
    {
      this.SetValue( Zoombox.AreZoomModifiersActivePropertyKey, value );
    }

    #endregion

    #region AreZoomToSelectionModifiersActive Property

    private static readonly DependencyPropertyKey AreZoomToSelectionModifiersActivePropertyKey =
      DependencyProperty.RegisterReadOnly( "AreZoomToSelectionModifiersActive", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty AreZoomToSelectionModifiersActiveProperty = Zoombox.AreZoomToSelectionModifiersActivePropertyKey.DependencyProperty;

    public bool AreZoomToSelectionModifiersActive
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.AreZoomToSelectionModifiersActiveProperty );
      }
    }

    private void SetAreZoomToSelectionModifiersActive( bool value )
    {
      this.SetValue( Zoombox.AreZoomToSelectionModifiersActivePropertyKey, value );
    }

    #endregion

    #region AutoWrapContentWithViewbox Property

    public static readonly DependencyProperty AutoWrapContentWithViewboxProperty =
      DependencyProperty.Register( "AutoWrapContentWithViewbox", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( true, 
          new PropertyChangedCallback( Zoombox.OnAutoWrapContentWithViewboxChanged ) ) );

    public bool AutoWrapContentWithViewbox
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.AutoWrapContentWithViewboxProperty );
      }
      set
      {
        this.SetValue( Zoombox.AutoWrapContentWithViewboxProperty, value );
      }
    }

    private static void OnAutoWrapContentWithViewboxChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      o.CoerceValue( Zoombox.ContentProperty );
    }

    private static object CoerceContentValue( DependencyObject d, object value )
    {
      return ( ( Zoombox )d ).CoerceContentValue( value );
    }

    private object CoerceContentValue( object value )
    {
      if( value != null && !( value is UIElement ) && !( ( bool )this.GetValue( DesignerProperties.IsInDesignModeProperty ) ) )
        throw new InvalidContentException( ErrorMessages.GetMessage( "ZoomboxContentMustBeUIElement" ) );

      object oldContent = _content;
      if( value != _trueContent || ( this.IsContentWrapped != this.AutoWrapContentWithViewbox ) )
      {
        // check whether the content is currently wrapped and needs to be unwrapped
        if( this.IsContentWrapped && _content is Viewbox && _content != _trueContent )
        {
          Viewbox viewbox = ( Viewbox )_content;

          BindingOperations.ClearAllBindings( viewbox );
          ( viewbox as Viewbox ).Child = null;

          this.RemoveLogicalChild( viewbox );
        }

        // make sure the view finder's visual brush is null
        if( _viewFinderDisplay != null && _viewFinderDisplay.VisualBrush != null )
        {
          _viewFinderDisplay.VisualBrush.Visual = null;
          _viewFinderDisplay.VisualBrush = null;
        }

        // update the cached content and true content values
        _content = value as UIElement;
        _trueContent = value as UIElement;

        // if necessary, unparent the existing content
        if( _contentPresenter != null && _contentPresenter.Content != null )
        {
          _contentPresenter.Content = null;
        }

        // if necessary, wrap the content
        this.IsContentWrapped = false;
        if( this.AutoWrapContentWithViewbox )
        {
          // create a viewbox and make it the logical child of the Zoombox
          Viewbox viewbox = new Viewbox();
          this.AddLogicalChild( viewbox );

          // now set the new parent to be the viewbox
          viewbox.Child = value as UIElement;
          _content = viewbox;
          viewbox.HorizontalAlignment = HorizontalAlignment.Left;
          viewbox.VerticalAlignment = VerticalAlignment.Top;
          this.IsContentWrapped = true;
        }

        if( _contentPresenter != null )
        {
          _contentPresenter.Content = _content;
        }

        if( _viewFinderDisplay != null )
        {
          this.CreateVisualBrushForViewFinder( _content );
        }
        this.UpdateViewFinderDisplayContentBounds();
      }

      // if the content changes, we need to reset the flags used to first render and arrange the content
      if( oldContent != _content
          && this.HasArrangedContentPresenter
          && this.HasRenderedFirstView )
      {
        this.HasArrangedContentPresenter = false;
        this.HasRenderedFirstView = false;
        this.RefocusViewOnFirstRender = true;
        _contentPresenter.LayoutUpdated += new EventHandler( this.ContentPresenterFirstArranged );
      }
      return _content;
    }

    private UIElement _trueContent; //null

    #endregion

    #region CurrentView Property

    private static readonly DependencyPropertyKey CurrentViewPropertyKey =
      DependencyProperty.RegisterReadOnly( "CurrentView", typeof( ZoomboxView ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( ZoomboxView.Empty, 
          new PropertyChangedCallback( Zoombox.OnCurrentViewChanged ) ) );

    public static readonly DependencyProperty CurrentViewProperty = Zoombox.CurrentViewPropertyKey.DependencyProperty;

    public ZoomboxView CurrentView
    {
      get
      {
        return ( ZoomboxView )this.GetValue( Zoombox.CurrentViewProperty );
      }
    }

    private void SetCurrentView( ZoomboxView value )
    {
      this.SetValue( Zoombox.CurrentViewPropertyKey, value );
    }

    private static void OnCurrentViewChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = ( Zoombox )o;
      if( !zoombox.IsUpdatingView )
      {
        zoombox.ZoomTo( zoombox.CurrentView );
      }
      zoombox.RaiseEvent( new ZoomboxViewChangedEventArgs( e.OldValue as ZoomboxView, e.NewValue as ZoomboxView, zoombox._lastViewIndex, zoombox.CurrentViewIndex ) );
    }

    #endregion

    #region CurrentViewIndex Property

    private static readonly DependencyPropertyKey CurrentViewIndexPropertyKey =
      DependencyProperty.RegisterReadOnly( "CurrentViewIndex", typeof( int ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( -1 ) );

    public static readonly DependencyProperty CurrentViewIndexProperty = Zoombox.CurrentViewIndexPropertyKey.DependencyProperty;

    public int CurrentViewIndex
    {
      get
      {
        return ( int )this.GetValue( Zoombox.CurrentViewIndexProperty );
      }
    }

    internal void SetCurrentViewIndex( int value )
    {
      this.SetValue( Zoombox.CurrentViewIndexPropertyKey, value );
    }

    #endregion

    #region DragModifiers Property

    public static readonly DependencyProperty DragModifiersProperty =
      DependencyProperty.Register( "DragModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( Zoombox.GetDefaultDragModifiers() ) );

    [TypeConverter( typeof( KeyModifierCollectionConverter ) )]
    public KeyModifierCollection DragModifiers
    {
      get
      {
        return ( KeyModifierCollection )this.GetValue( Zoombox.DragModifiersProperty );
      }
      set
      {
        this.SetValue( Zoombox.DragModifiersProperty, value );
      }
    }

    private static KeyModifierCollection GetDefaultDragModifiers()
    {
      KeyModifierCollection result = new KeyModifierCollection();
      result.Add( KeyModifier.Ctrl );
      result.Add( KeyModifier.Exact );
      return result;
    }

    #endregion

    #region DragOnPreview Property

    public static readonly DependencyProperty DragOnPreviewProperty =
      DependencyProperty.Register( "DragOnPreview", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public bool DragOnPreview
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.DragOnPreviewProperty );
      }
      set
      {
        this.SetValue( Zoombox.DragOnPreviewProperty, value );
      }
    }

    #endregion

    #region EffectiveViewStackMode Property

    private static readonly DependencyPropertyKey EffectiveViewStackModePropertyKey =
      DependencyProperty.RegisterReadOnly( "EffectiveViewStackMode", typeof( ZoomboxViewStackMode ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( ZoomboxViewStackMode.Auto ) );

    public static readonly DependencyProperty EffectiveViewStackModeProperty = Zoombox.EffectiveViewStackModePropertyKey.DependencyProperty;

    public ZoomboxViewStackMode EffectiveViewStackMode
    {
      get
      {
        return ( ZoomboxViewStackMode )this.GetValue( Zoombox.EffectiveViewStackModeProperty );
      }
    }

    private void SetEffectiveViewStackMode( ZoomboxViewStackMode value )
    {
      this.SetValue( Zoombox.EffectiveViewStackModePropertyKey, value );
    }

    #endregion

    #region HasBackStack Property

    private static readonly DependencyPropertyKey HasBackStackPropertyKey =
      DependencyProperty.RegisterReadOnly( "HasBackStack", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty HasBackStackProperty = Zoombox.HasBackStackPropertyKey.DependencyProperty;

    public bool HasBackStack
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.HasBackStackProperty );
      }
    }

    #endregion

    #region HasForwardStack Property

    private static readonly DependencyPropertyKey HasForwardStackPropertyKey =
      DependencyProperty.RegisterReadOnly( "HasForwardStack", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty HasForwardStackProperty = Zoombox.HasForwardStackPropertyKey.DependencyProperty;

    public bool HasForwardStack
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.HasForwardStackProperty );
      }
    }

    #endregion

    #region IsAnimated Property

    public static readonly DependencyProperty IsAnimatedProperty =
      DependencyProperty.Register( "IsAnimated", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( true, 
          ( PropertyChangedCallback )null, new CoerceValueCallback( Zoombox.CoerceIsAnimatedValue ) ) );

    public bool IsAnimated
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.IsAnimatedProperty );
      }
      set
      {
        this.SetValue( Zoombox.IsAnimatedProperty, value );
      }
    }

    private static object CoerceIsAnimatedValue( DependencyObject d, object value )
    {
      Zoombox zoombox = ( Zoombox )d;
      bool result = ( bool )value;
      if( !zoombox.IsInitialized )
      {
        result = false;
      }
      return result;
    }

    #endregion

    #region IsDraggingContent Property

    private static readonly DependencyPropertyKey IsDraggingContentPropertyKey =
      DependencyProperty.RegisterReadOnly( "IsDraggingContent", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty IsDraggingContentProperty = Zoombox.IsDraggingContentPropertyKey.DependencyProperty;

    public bool IsDraggingContent
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.IsDraggingContentProperty );
      }
    }

    private void SetIsDraggingContent( bool value )
    {
      this.SetValue( Zoombox.IsDraggingContentPropertyKey, value );
    }

    #endregion

    #region IsSelectingRegion Property

    private static readonly DependencyPropertyKey IsSelectingRegionPropertyKey =
      DependencyProperty.RegisterReadOnly( "IsSelectingRegion", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public static readonly DependencyProperty IsSelectingRegionProperty = Zoombox.IsSelectingRegionPropertyKey.DependencyProperty;

    public bool IsSelectingRegion
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.IsSelectingRegionProperty );
      }
    }

    private void SetIsSelectingRegion( bool value )
    {
      this.SetValue( Zoombox.IsSelectingRegionPropertyKey, value );
    }

    #endregion

    #region IsUsingScrollBars Property

    public static readonly DependencyProperty IsUsingScrollBarsProperty =
      DependencyProperty.Register( "IsUsingScrollBars", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false, ( PropertyChangedCallback )null ) );

    public bool IsUsingScrollBars
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.IsUsingScrollBarsProperty );
      }
      set
      {
        this.SetValue( Zoombox.IsUsingScrollBarsProperty, value );
      }
    }

    #endregion

    #region MaxScale Property

    public static readonly DependencyProperty MaxScaleProperty =
      DependencyProperty.Register( "MaxScale", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( 100d, FrameworkPropertyMetadataOptions.AffectsMeasure,
          new PropertyChangedCallback( Zoombox.OnMaxScaleChanged ), new CoerceValueCallback( Zoombox.CoerceMaxScaleValue ) ) );

    public double MaxScale
    {
      get
      {
        return ( double )this.GetValue( Zoombox.MaxScaleProperty );
      }
      set
      {
        this.SetValue( Zoombox.MaxScaleProperty, value );
      }
    }

    private static void OnMaxScaleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = ( Zoombox )o;
      zoombox.CoerceValue( Zoombox.MinScaleProperty );
      zoombox.CoerceValue( Zoombox.ScaleProperty );
    }

    private static object CoerceMaxScaleValue( DependencyObject d, object value )
    {
      Zoombox zoombox = ( Zoombox )d;
      double result = ( double )value;
      if( result < zoombox.MinScale )
      {
        result = zoombox.MinScale;
      }
      return result;
    }

    #endregion

    #region MinScale Property

    public static readonly DependencyProperty MinScaleProperty =
      DependencyProperty.Register( "MinScale", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( 0.01d, FrameworkPropertyMetadataOptions.AffectsMeasure,
          new PropertyChangedCallback( Zoombox.OnMinScaleChanged ), new CoerceValueCallback( Zoombox.CoerceMinScaleValue ) ) );

    public double MinScale
    {
      get
      {
        return ( double )this.GetValue( Zoombox.MinScaleProperty );
      }
      set
      {
        this.SetValue( Zoombox.MinScaleProperty, value );
      }
    }

    private static void OnMinScaleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = ( Zoombox )o;
      zoombox.CoerceValue( Zoombox.MinScaleProperty );
      zoombox.CoerceValue( Zoombox.ScaleProperty );
    }

    private static object CoerceMinScaleValue( DependencyObject d, object value )
    {
      Zoombox zoombox = ( Zoombox )d;
      double result = ( double )value;
      if( result > zoombox.MaxScale )
      {
        result = zoombox.MaxScale;
      }
      return result;
    }

    #endregion

    #region NavigateOnPreview Property

    public static readonly DependencyProperty NavigateOnPreviewProperty =
      DependencyProperty.Register( "NavigateOnPreview", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false ) );

    public bool NavigateOnPreview
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.NavigateOnPreviewProperty );
      }
      set
      {
        this.SetValue( Zoombox.NavigateOnPreviewProperty, value );
      }
    }

    #endregion

    #region PanDistance Property

    public static readonly DependencyProperty PanDistanceProperty =
      DependencyProperty.Register( "PanDistance", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( 5d ) );

    public double PanDistance
    {
      get
      {
        return ( double )this.GetValue( Zoombox.PanDistanceProperty );
      }
      set
      {
        this.SetValue( Zoombox.PanDistanceProperty, value );
      }
    }

    #endregion

    #region Position Property

    public static readonly DependencyProperty PositionProperty =
      DependencyProperty.Register( "Position", typeof( Point ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( PointHelper.Empty, new PropertyChangedCallback( Zoombox.OnPositionChanged ) ) );

    public Point Position
    {
      get
      {
        return ( Point )this.GetValue( Zoombox.PositionProperty );
      }
      set
      {
        this.SetValue( Zoombox.PositionProperty, value );
      }
    }

    private static void OnPositionChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = ( Zoombox )o;
      if( !zoombox.IsUpdatingViewport )
      {
        Point newPosition = ( Point )e.NewValue;
        double scale = zoombox.Scale;
        if( scale > 0 )
        {
          zoombox.ZoomTo( new Point( -newPosition.X, -newPosition.Y ) );
        }
      }
    }

    #endregion

    #region RelativeZoomModifiers Property

    public static readonly DependencyProperty RelativeZoomModifiersProperty =
      DependencyProperty.Register( "RelativeZoomModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( Zoombox.GetDefaultRelativeZoomModifiers() ) );

    [TypeConverter( typeof( KeyModifierCollectionConverter ) )]
    public KeyModifierCollection RelativeZoomModifiers
    {
      get
      {
        return ( KeyModifierCollection )this.GetValue( Zoombox.RelativeZoomModifiersProperty );
      }
      set
      {
        this.SetValue( Zoombox.RelativeZoomModifiersProperty, value );
      }
    }

    private static KeyModifierCollection GetDefaultRelativeZoomModifiers()
    {
      KeyModifierCollection result = new KeyModifierCollection();
      result.Add( KeyModifier.Ctrl );
      result.Add( KeyModifier.Alt );
      result.Add( KeyModifier.Exact );
      return result;
    }

    #endregion

    #region Scale Property

    public static readonly DependencyProperty ScaleProperty =
      DependencyProperty.Register( "Scale", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( double.NaN, 
          new PropertyChangedCallback( Zoombox.OnScaleChanged ), new CoerceValueCallback( Zoombox.CoerceScaleValue ) ) );

    public double Scale
    {
      get
      {
        return ( double )this.GetValue( Zoombox.ScaleProperty );
      }
      set
      {
        this.SetValue( Zoombox.ScaleProperty, value );
      }
    }

    private static void OnScaleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = ( Zoombox )o;
      if( !zoombox.IsUpdatingView )
      {
        double newScale = ( double )e.NewValue;
        zoombox.ZoomTo( newScale );
      }
    }

    private static object CoerceScaleValue( DependencyObject d, object value )
    {
      Zoombox zoombox = ( Zoombox )d;
      double result = ( double )value;

      if( result < zoombox.MinScale )
      {
        result = zoombox.MinScale;
      }

      if( result > zoombox.MaxScale )
      {
        result = zoombox.MaxScale;
      }

      return result;
    }

    #endregion

    #region ViewFinder Property

    private static readonly DependencyPropertyKey ViewFinderPropertyKey =
      DependencyProperty.RegisterReadOnly( "ViewFinder", typeof( FrameworkElement ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( null ) );

    public static readonly DependencyProperty ViewFinderProperty = Zoombox.ViewFinderPropertyKey.DependencyProperty;

    public FrameworkElement ViewFinder
    {
      get
      {
        return ( FrameworkElement )this.GetValue( Zoombox.ViewFinderProperty );
      }
    }

    #endregion

    #region ViewFinderVisibility Attached Property

    public static readonly DependencyProperty ViewFinderVisibilityProperty =
      DependencyProperty.RegisterAttached( "ViewFinderVisibility", typeof( Visibility ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( Visibility.Visible ) );

    public static Visibility GetViewFinderVisibility( DependencyObject d )
    {
      return ( Visibility )( d.GetValue( Zoombox.ViewFinderVisibilityProperty ) );
    }

    public static void SetViewFinderVisibility( DependencyObject d, Visibility value )
    {
      d.SetValue( Zoombox.ViewFinderVisibilityProperty, value );
    }

    #endregion

    #region Viewport Property

    private static readonly DependencyPropertyKey ViewportPropertyKey =
      DependencyProperty.RegisterReadOnly( "Viewport", typeof( Rect ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( Rect.Empty, 
          new PropertyChangedCallback( Zoombox.OnViewportChanged ) ) );

    public static readonly DependencyProperty ViewportProperty = Zoombox.ViewportPropertyKey.DependencyProperty;

    public Rect Viewport
    {
      get
      {
        return ( Rect )this.GetValue( Zoombox.ViewportProperty );
      }
    }

    private static void OnViewportChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      // keep the Position property in sync with the Viewport
      Zoombox zoombox = ( Zoombox )o;
      zoombox.Position = new Point( -zoombox.Viewport.Left * zoombox.Scale / zoombox._viewboxFactor, -zoombox.Viewport.Top * zoombox.Scale / zoombox._viewboxFactor );
    }

    #endregion

    #region ViewStackCount Property

    private static readonly DependencyPropertyKey ViewStackCountPropertyKey =
      DependencyProperty.RegisterReadOnly( "ViewStackCount", typeof( int ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( -1,
          new PropertyChangedCallback( Zoombox.OnViewStackCountChanged ) ) );

    public static readonly DependencyProperty ViewStackCountProperty = Zoombox.ViewStackCountPropertyKey.DependencyProperty;

    public int ViewStackCount
    {
      get
      {
        return ( int )this.GetValue( Zoombox.ViewStackCountProperty );
      }
    }

    internal void SetViewStackCount( int value )
    {
      this.SetValue( Zoombox.ViewStackCountPropertyKey, value );
    }

    private static void OnViewStackCountChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( Zoombox )d ).OnViewStackCountChanged( e );
    }

    private void OnViewStackCountChanged( DependencyPropertyChangedEventArgs e )
    {
      if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
        return;

      this.UpdateStackProperties();
    }

    #endregion

    #region ViewStackIndex Property

    public static readonly DependencyProperty ViewStackIndexProperty =
      DependencyProperty.Register( "ViewStackIndex", typeof( int ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( -1,
          new PropertyChangedCallback( Zoombox.OnViewStackIndexChanged ), new CoerceValueCallback( Zoombox.CoerceViewStackIndexValue ) ) );

    public int ViewStackIndex
    {
      get
      {
        return ( int )this.GetValue( Zoombox.ViewStackIndexProperty );
      }
      set
      {
        this.SetValue( Zoombox.ViewStackIndexProperty, value );
      }
    }

    private static void OnViewStackIndexChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( Zoombox )d ).OnViewStackIndexChanged( e );
    }

    private void OnViewStackIndexChanged( DependencyPropertyChangedEventArgs e )
    {
      if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
        return;

      if( !this.IsUpdatingView )
      {
        int viewIndex = this.ViewStackIndex;
        if( viewIndex >= 0 && viewIndex < ViewStack.Count )
        {
          // update the current view, but don't allow the new view 
          // to be added to the view stack
          this.UpdateView( this.ViewStack[ viewIndex ], true, false, viewIndex );
        }
      }

      this.UpdateStackProperties();
      this.RaiseEvent( new IndexChangedEventArgs( Zoombox.ViewStackIndexChangedEvent, ( int )e.OldValue, ( int )e.NewValue ) );
    }

    private static object CoerceViewStackIndexValue( DependencyObject d, object value )
    {
      Zoombox zoombox = d as Zoombox;
      return ( zoombox.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled ) ? -1 : value;
    }

    #endregion

    #region ViewStackMode Property

    public static readonly DependencyProperty ViewStackModeProperty = 
      DependencyProperty.Register( "ViewStackMode", typeof( ZoomboxViewStackMode ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( ZoomboxViewStackMode.Default,
          new PropertyChangedCallback( Zoombox.OnViewStackModeChanged ), new CoerceValueCallback( Zoombox.CoerceViewStackModeValue ) ) );

    public ZoomboxViewStackMode ViewStackMode
    {
      get
      {
        return ( ZoomboxViewStackMode )this.GetValue( Zoombox.ViewStackModeProperty );
      }
      set
      {
        this.SetValue( Zoombox.ViewStackModeProperty, value );
      }
    }

    private static void OnViewStackModeChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( Zoombox )d ).OnViewStackModeChanged( e );
    }

    private void OnViewStackModeChanged( DependencyPropertyChangedEventArgs e )
    {
      if( ( ZoomboxViewStackMode )e.NewValue == ZoomboxViewStackMode.Disabled && _viewStack != null )
      {
        _viewStack.ClearViewStackSource();
        _viewStack = null;
      }
    }

    private static object CoerceViewStackModeValue( DependencyObject d, object value )
    {
      Zoombox zoombox = d as Zoombox;
      ZoomboxViewStackMode effectiveMode = ( ZoomboxViewStackMode )value;

      // if the effective mode is currently disabled, it must be updated first
      if( zoombox.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
      {
        zoombox.SetEffectiveViewStackMode( effectiveMode );
      }

      // now determine the correct effective mode
      if( effectiveMode != ZoomboxViewStackMode.Disabled )
      {
        if( effectiveMode == ZoomboxViewStackMode.Default )
        {
          effectiveMode = ( zoombox.ViewStack.AreViewsFromSource ? ZoomboxViewStackMode.Manual : ZoomboxViewStackMode.Auto );
        }
        if( zoombox.ViewStack.AreViewsFromSource && ( ZoomboxViewStackMode )effectiveMode != ZoomboxViewStackMode.Manual )
        {
          throw new InvalidOperationException( ErrorMessages.GetMessage( "ViewModeInvalidForSource" ) );
        }
      }

      // update the effective mode
      zoombox.SetEffectiveViewStackMode( effectiveMode );
      return value;
    }

    #endregion

    #region ViewStackSource Property

    public static readonly DependencyProperty ViewStackSourceProperty =
      DependencyProperty.Register( "ViewStackSource", typeof( IEnumerable ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( ( IEnumerable )null,
          new PropertyChangedCallback( Zoombox.OnViewStackSourceChanged ) ) );

    [Bindable( true )]
    public IEnumerable ViewStackSource
    {
      get
      {
        return ( _viewStack == null ) ? null : ViewStack.Source;
      }
      set
      {
        if( value == null )
        {
          this.ClearValue( Zoombox.ViewStackSourceProperty );
        }
        else
        {
          this.SetValue( Zoombox.ViewStackSourceProperty, value );
        }
      }
    }

    private static void OnViewStackSourceChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = ( Zoombox )d;
      IEnumerable oldValue = ( IEnumerable )e.OldValue;
      IEnumerable newValue = ( IEnumerable )e.NewValue;

      // We need to know whether the new value represents an explicit null value 
      // or whether it came from a binding. The latter indicates that we stay in ViewStackSource mode,
      // but with a null collection.
      if( e.NewValue == null && !BindingOperations.IsDataBound( d, Zoombox.ViewStackSourceProperty ) )
      {
        if( zoombox.ViewStack != null )
        {
          zoombox.ViewStack.ClearViewStackSource();
        }
      }
      else
      {
        zoombox.ViewStack.SetViewStackSource( newValue );
      }

      zoombox.CoerceValue( Zoombox.ViewStackModeProperty );
    }

    #endregion

    #region ZoomModifiers Property

    public static readonly DependencyProperty ZoomModifiersProperty =
      DependencyProperty.Register( "ZoomModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( Zoombox.GetDefaultZoomModifiers() ) );

    [TypeConverter( typeof( KeyModifierCollectionConverter ) )]
    public KeyModifierCollection ZoomModifiers
    {
      get
      {
        return ( KeyModifierCollection )this.GetValue( Zoombox.ZoomModifiersProperty );
      }
      set
      {
        this.SetValue( Zoombox.ZoomModifiersProperty, value );
      }
    }

    private static KeyModifierCollection GetDefaultZoomModifiers()
    {
      KeyModifierCollection result = new KeyModifierCollection();
      result.Add( KeyModifier.Shift );
      result.Add( KeyModifier.Exact );
      return result;
    }

    #endregion

    #region ZoomOnPreview Property

    public static readonly DependencyProperty ZoomOnPreviewProperty =
      DependencyProperty.Register( "ZoomOnPreview", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( true ) );

    public bool ZoomOnPreview
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.ZoomOnPreviewProperty );
      }
      set
      {
        this.SetValue( Zoombox.ZoomOnPreviewProperty, value );
      }
    }

    #endregion

    #region ZoomOrigin Property

    public static readonly DependencyProperty ZoomOriginProperty =
      DependencyProperty.Register( "ZoomOrigin", typeof( Point ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( new Point( 0.5d, 0.5d ) ) );

    public Point ZoomOrigin
    {
      get
      {
        return ( Point )this.GetValue( Zoombox.ZoomOriginProperty );
      }
      set
      {
        this.SetValue( Zoombox.ZoomOriginProperty, value );
      }
    }

    #endregion

    #region ZoomPercentage Property

    public static readonly DependencyProperty ZoomPercentageProperty =
      DependencyProperty.Register( "ZoomPercentage", typeof( double ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( 5d ) );

    public double ZoomPercentage
    {
      get
      {
        return ( double )this.GetValue( Zoombox.ZoomPercentageProperty );
      }
      set
      {
        this.SetValue( Zoombox.ZoomPercentageProperty, value );
      }
    }

    #endregion

    #region ZoomOn Property

    public static readonly DependencyProperty ZoomOnProperty =
      DependencyProperty.Register( "ZoomOn", typeof( ZoomboxZoomOn ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( ZoomboxZoomOn.Content ) );

    public ZoomboxZoomOn ZoomOn
    {
      get
      {
        return ( ZoomboxZoomOn )this.GetValue( Zoombox.ZoomOnProperty );
      }
      set
      {
        this.SetValue( Zoombox.ZoomOnProperty, value );
      }
    }

    #endregion

    #region ZoomToSelectionModifiers Property

    public static readonly DependencyProperty ZoomToSelectionModifiersProperty =
      DependencyProperty.Register( "ZoomToSelectionModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( Zoombox.GetDefaultZoomToSelectionModifiers() ) );

    [TypeConverter( typeof( KeyModifierCollectionConverter ) )]
    public KeyModifierCollection ZoomToSelectionModifiers
    {
      get
      {
        return ( KeyModifierCollection )this.GetValue( Zoombox.ZoomToSelectionModifiersProperty );
      }
      set
      {
        this.SetValue( Zoombox.ZoomToSelectionModifiersProperty, value );
      }
    }

    private static KeyModifierCollection GetDefaultZoomToSelectionModifiers()
    {
      KeyModifierCollection result = new KeyModifierCollection();
      result.Add( KeyModifier.Alt );
      result.Add( KeyModifier.Exact );
      return result;
    }

    #endregion

    #region KeepContentInBounds Property

    public static readonly DependencyProperty KeepContentInBoundsProperty =
      DependencyProperty.Register( "KeepContentInBounds", typeof( bool ), typeof( Zoombox ),
        new FrameworkPropertyMetadata( false, 
          new PropertyChangedCallback( Zoombox.OnKeepContentInBoundsChanged ) ) );

    public bool KeepContentInBounds
    {
      get
      {
        return ( bool )this.GetValue( Zoombox.KeepContentInBoundsProperty );
      }
      set
      {
        this.SetValue( Zoombox.KeepContentInBoundsProperty, value );
      }
    }

    private static void OnKeepContentInBoundsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
      ( ( Zoombox )d ).OnKeepContentInBoundsChanged( e );
    }

    private void OnKeepContentInBoundsChanged( DependencyPropertyChangedEventArgs e )
    {
      //
      // Update view and see if we need to reposition the content
      //
      bool oldIsAnimated = this.IsAnimated;
      this.IsAnimated = false;
      try
      {
        this.UpdateView( this.CurrentView, false, false, this.ViewStackIndex );
      }
      finally
      {
        this.IsAnimated = oldIsAnimated;
      }
    }

    #endregion

    #region ViewStack Property

    public ZoomboxViewStack ViewStack
    {
      get
      {
        if( _viewStack == null && this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
        {
          _viewStack = new ZoomboxViewStack( this );
        }
        return _viewStack;
      }
    }

    #endregion

    #region HasArrangedContentPresenter Internal Property

    internal bool HasArrangedContentPresenter
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.HasArrangedContentPresenter ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.HasArrangedContentPresenter ] = value;
      }
    }

    #endregion

    #region IsUpdatingView Internal Property

    internal bool IsUpdatingView
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.IsUpdatingView ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.IsUpdatingView ] = value;
      }
    }

    #endregion

    #region ContentOffset Private Property

    private Vector ContentOffset
    {
      get
      {
        // auto-wrapped content is always left and top aligned
        if( this.IsContentWrapped || _content == null || !( _content is FrameworkElement ) )
          return new Vector( 0, 0 );

        double x = 0;
        double y = 0;
        Size contentSize = this.ContentRect.Size;

        switch( ( _content as FrameworkElement ).HorizontalAlignment )
        {
          case HorizontalAlignment.Center:
          case HorizontalAlignment.Stretch:
            x = ( this.RenderSize.Width - contentSize.Width ) / 2;
            break;

          case HorizontalAlignment.Right:
            x = this.RenderSize.Width - contentSize.Width;
            break;
        }

        switch( ( _content as FrameworkElement ).VerticalAlignment )
        {
          case VerticalAlignment.Center:
          case VerticalAlignment.Stretch:
            y = ( this.RenderSize.Height - contentSize.Height ) / 2;
            break;

          case VerticalAlignment.Bottom:
            y = this.RenderSize.Height - contentSize.Height;
            break;
        }

        return new Vector( x, y );
      }
    }

    #endregion

    #region ContentRect Private Property

    private Rect ContentRect
    {
      get
      {
        return ( _content == null ) ? Rect.Empty
            : new Rect( new Size( _content.RenderSize.Width / _viewboxFactor, _content.RenderSize.Height / _viewboxFactor ) );
      }
    }

    #endregion

    #region HasRenderedFirstView Private Property

    private bool HasRenderedFirstView
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.HasRenderedFirstView ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.HasRenderedFirstView ] = value;
      }
    }

    #endregion

    #region HasUIPermission Private Property

    private bool HasUIPermission
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.HasUIPermission ];
      }
    }

    #endregion

    #region IsContentWrapped Private Property

    private bool IsContentWrapped
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.IsContentWrapped ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.IsContentWrapped ] = value;
      }
    }

    #endregion

    #region IsDraggingViewport Private Property

    private bool IsDraggingViewport
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.IsDraggingViewport ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.IsDraggingViewport ] = value;
      }
    }

    #endregion

    #region IsMonitoringInput Private Property

    private bool IsMonitoringInput
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.IsMonitoringInput ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.IsMonitoringInput ] = value;
      }
    }

    #endregion

    #region IsResizingViewport Private Property

    private bool IsResizingViewport
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.IsResizingViewport ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.IsResizingViewport ] = value;
      }
    }

    #endregion

    #region IsUpdatingViewport Private Property

    private bool IsUpdatingViewport
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.IsUpdatingViewport ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.IsUpdatingViewport ] = value;
      }
    }

    #endregion

    #region RefocusViewOnFirstRender Private Property

    private bool RefocusViewOnFirstRender
    {
      get
      {
        return _cacheBits[ ( int )CacheBits.RefocusViewOnFirstRender ];
      }
      set
      {
        _cacheBits[ ( int )CacheBits.RefocusViewOnFirstRender ] = value;
      }
    }

    #endregion

    #region ViewFinderDisplayRect Private Property

    private Rect ViewFinderDisplayRect
    {
      get
      {
        return ( _viewFinderDisplay == null ) ? Rect.Empty
            : new Rect( new Point( 0, 0 ), new Point( _viewFinderDisplay.RenderSize.Width, _viewFinderDisplay.RenderSize.Height ) );
      }
    }

    #endregion

    #region AnimationBeginning Event

    public static readonly RoutedEvent AnimationBeginningEvent = EventManager.RegisterRoutedEvent( "AnimationBeginning", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( Zoombox ) );

    public event RoutedEventHandler AnimationBeginning
    {
      add
      {
        this.AddHandler( Zoombox.AnimationBeginningEvent, value );
      }
      remove
      {
        this.RemoveHandler( Zoombox.AnimationBeginningEvent, value );
      }
    }

    #endregion

    #region AnimationCompleted Event

    public static readonly RoutedEvent AnimationCompletedEvent = EventManager.RegisterRoutedEvent( "AnimationCompleted", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( Zoombox ) );

    public event RoutedEventHandler AnimationCompleted
    {
      add
      {
        this.AddHandler( Zoombox.AnimationCompletedEvent, value );
      }
      remove
      {
        this.RemoveHandler( Zoombox.AnimationCompletedEvent, value );
      }
    }

    #endregion

    #region CurrentViewChanged Event

    public static readonly RoutedEvent CurrentViewChangedEvent = EventManager.RegisterRoutedEvent( "CurrentViewChanged", RoutingStrategy.Bubble, typeof( ZoomboxViewChangedEventHandler ), typeof( Zoombox ) );

    public event ZoomboxViewChangedEventHandler CurrentViewChanged
    {
      add
      {
        this.AddHandler( Zoombox.CurrentViewChangedEvent, value );
      }
      remove
      {
        this.RemoveHandler( Zoombox.CurrentViewChangedEvent, value );
      }
    }

    #endregion

    public event EventHandler<ScrollEventArgs> Scroll;

    #region ViewStackIndexChanged Event

    public static readonly RoutedEvent ViewStackIndexChangedEvent = EventManager.RegisterRoutedEvent( "ViewStackIndexChanged", RoutingStrategy.Bubble, typeof( IndexChangedEventHandler ), typeof( Zoombox ) );

    public event IndexChangedEventHandler ViewStackIndexChanged
    {
      add
      {
        this.AddHandler( Zoombox.ViewStackIndexChangedEvent, value );
      }
      remove
      {
        this.RemoveHandler( Zoombox.ViewStackIndexChangedEvent, value );
      }
    }

    #endregion

    #region Back Command

    public static RoutedUICommand Back = new RoutedUICommand( "Go Back", "GoBack", typeof( Zoombox ) );

    private void CanGoBack( object sender, CanExecuteRoutedEventArgs e )
    {
      e.CanExecute = ( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
                  && ( this.ViewStackIndex > 0 );
    }

    private void GoBack( object sender, ExecutedRoutedEventArgs e )
    {
      this.GoBack();
    }

    #endregion

    #region Center Command

    public static RoutedUICommand Center = new RoutedUICommand( "Center Content", "Center", typeof( Zoombox ) );

    private void CenterContent( object sender, ExecutedRoutedEventArgs e )
    {
      this.CenterContent();
    }

    #endregion

    #region Fill Command

    public static RoutedUICommand Fill = new RoutedUICommand( "Fill Bounds with Content", "FillToBounds", typeof( Zoombox ) );

    private void FillToBounds( object sender, ExecutedRoutedEventArgs e )
    {
      this.FillToBounds();
    }

    #endregion

    #region Fit Command

    public static RoutedUICommand Fit = new RoutedUICommand( "Fit Content within Bounds", "FitToBounds", typeof( Zoombox ) );

    private void FitToBounds( object sender, ExecutedRoutedEventArgs e )
    {
      this.FitToBounds();
    }

    #endregion

    #region Forward Command

    public static RoutedUICommand Forward = new RoutedUICommand( "Go Forward", "GoForward", typeof( Zoombox ) );

    private void CanGoForward( object sender, CanExecuteRoutedEventArgs e )
    {
      e.CanExecute = ( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
                  && ( this.ViewStackIndex < this.ViewStack.Count - 1 );
    }

    private void GoForward( object sender, ExecutedRoutedEventArgs e )
    {
      this.GoForward();
    }

    #endregion

    #region Home Command

    public static RoutedUICommand Home = new RoutedUICommand( "Go Home", "GoHome", typeof( Zoombox ) );

    private void CanGoHome( object sender, CanExecuteRoutedEventArgs e )
    {
      e.CanExecute = ( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
                  && ( this.ViewStack.Count > 0 )
                  && ( this.ViewStackIndex != 0 );
    }

    private void GoHome( object sender, ExecutedRoutedEventArgs e )
    {
      this.GoHome();
    }

    #endregion

    #region PanDown Command

    public static RoutedUICommand PanDown = new RoutedUICommand( "Pan Down", "PanDown", typeof( Zoombox ) );

    private void PanDownExecuted( object sender, ExecutedRoutedEventArgs e )
    {
      this.Position = new Point( _basePosition.X, _basePosition.Y + PanDistance );
    }

    #endregion

    #region PanLeft Command

    public static RoutedUICommand PanLeft = new RoutedUICommand( "Pan Left", "PanLeft", typeof( Zoombox ) );

    private void PanLeftExecuted( object sender, ExecutedRoutedEventArgs e )
    {
      this.Position = new Point( _basePosition.X - this.PanDistance, _basePosition.Y );
    }

    #endregion

    #region PanRight Command

    public static RoutedUICommand PanRight = new RoutedUICommand( "Pan Right", "PanRight", typeof( Zoombox ) );

    private void PanRightExecuted( object sender, ExecutedRoutedEventArgs e )
    {
      this.Position = new Point( _basePosition.X + this.PanDistance, _basePosition.Y );
    }

    #endregion

    #region PanUp Command

    public static RoutedUICommand PanUp = new RoutedUICommand( "Pan Up", "PanUp", typeof( Zoombox ) );

    private void PanUpExecuted( object sender, ExecutedRoutedEventArgs e )
    {
      this.Position = new Point( _basePosition.X, _basePosition.Y - this.PanDistance );
    }

    #endregion

    #region Refocus Command

    public static RoutedUICommand Refocus = new RoutedUICommand( "Refocus View", "Refocus", typeof( Zoombox ) );

    private void CanRefocusView( object sender, CanExecuteRoutedEventArgs e )
    {
      e.CanExecute = ( this.EffectiveViewStackMode == ZoomboxViewStackMode.Manual )
                  && ( this.ViewStackIndex >= 0 && this.ViewStackIndex < this.ViewStack.Count )
                  && ( this.CurrentView != this.ViewStack[ this.ViewStackIndex ] );
    }

    private void RefocusView( object sender, ExecutedRoutedEventArgs e )
    {
      this.RefocusView();
    }

    #endregion

    #region ZoomIn Command

    public static RoutedUICommand ZoomIn = new RoutedUICommand( "Zoom In", "ZoomIn", typeof( Zoombox ) );

    private void ZoomInExecuted( object sender, ExecutedRoutedEventArgs e )
    {
      this.Zoom( this.ZoomPercentage / 100 );
    }

    #endregion

    #region ZoomOut Command

    public static RoutedUICommand ZoomOut = new RoutedUICommand( "Zoom Out", "ZoomOut", typeof( Zoombox ) );

    private void ZoomOutExecuted( object sender, ExecutedRoutedEventArgs e )
    {
      this.Zoom( -this.ZoomPercentage / 100 );
    }

    #endregion

    public void CenterContent()
    {
      if( _content != null )
      {
        this.SetScrollBars();
        this.ZoomTo( ZoomboxView.Center );
      }
    }

    public void FillToBounds()
    {
      if( _content != null )
      {
        this.SetScrollBars();
        this.ZoomTo( ZoomboxView.Fill );
      }
    }

    public void FitToBounds()
    {
      if( _content != null )
      {
        this.SetScrollBars();
        this.ZoomTo( ZoomboxView.Fit );
      }
    }

    public void GoBack()
    {
      if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
        return;

      if( this.ViewStackIndex > 0 )
      {
        this.ViewStackIndex--;
      }
    }

    public void GoForward()
    {
      if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
        return;

      if( this.ViewStackIndex < this.ViewStack.Count - 1 )
      {
        this.ViewStackIndex++;
      }
    }

    public void GoHome()
    {
      if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
        return;

      if( this.ViewStackIndex > 0 )
      {
        this.ViewStackIndex = 0;
      }
    }

    public override void OnApplyTemplate()
    {
      this.AttachToVisualTree();
      base.OnApplyTemplate();
    }

    public void RefocusView()
    {
      if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
        return;

      if( this.ViewStackIndex >= 0 && this.ViewStackIndex < this.ViewStack.Count
          && this.CurrentView != this.ViewStack[ this.ViewStackIndex ] )
      {
        this.UpdateView( this.ViewStack[ this.ViewStackIndex ], true, false, this.ViewStackIndex );
      }
    }

    public void Zoom( double percentage )
    {
      // if there is nothing to scale, just return
      if( _content == null )
        return;

      this.Zoom( percentage, this.GetZoomRelativePoint() );
    }

    public void Zoom( double percentage, Point relativeTo )
    {
      // if there is nothing to scale, just return
      if( _content == null )
        return;

      // adjust the current scale relative to the given point
      double scale = this.Scale * ( 1 + percentage );
      this.ZoomTo( scale, relativeTo );
    }

    public void ZoomTo( Point position )
    {
      // if there is nothing to pan, just return
      if( _content == null )
        return;

      // zoom to the new region
      this.ZoomTo( new ZoomboxView( new Point( -position.X, -position.Y ) ) );
    }

    public void ZoomTo( Rect region )
    {
      if( _content == null )
        return;

      // adjust the current scale and position
      this.UpdateView( new ZoomboxView( region ), true, true );
    }

    public void ZoomTo( double scale )
    {
      // if there is nothing to scale, just return
      if( _content == null )
        return;

      // adjust the current scale relative to the center of the content within the control
      this.ZoomTo( scale, true );
    }

    public void ZoomTo( double scale, Point relativeTo )
    {
      this.ZoomTo( scale, relativeTo, true, true );
    }

    public void ZoomTo( ZoomboxView view )
    {
      this.UpdateView( view, true, true );
    }

    internal void UpdateStackProperties()
    {
      this.SetValue( Zoombox.HasBackStackPropertyKey, this.ViewStackIndex > 0 );
      this.SetValue( Zoombox.HasForwardStackPropertyKey, this.ViewStack.Count > this.ViewStackIndex + 1 );
      CommandManager.InvalidateRequerySuggested();
    }

    protected override Size MeasureOverride( Size constraint )
    {
      if( _content != null )
      {
        // measure visuals according to supplied constraint
        Size size = base.MeasureOverride( constraint );

        // now re-measure content to let the child be whatever size it desires
        _content.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity ) );
        return size;
      }


      // avoid returning infinity
      if( double.IsInfinity( constraint.Height ) )
      {
        constraint.Height = 0;
      }

      if( double.IsInfinity( constraint.Width ) )
      {
        constraint.Width = 0;
      }
      return constraint;
    }

    protected override void OnContentChanged( object oldContent, object newContent )
    {
      // disconnect SizeChanged handler from old content
      if( oldContent is FrameworkElement )
      {
        ( oldContent as FrameworkElement ).RemoveHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ) );
      }
      else
      {
        this.RemoveHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ) );
      }

      // connect SizeChanged handler to new content
      if( _content is FrameworkElement )
      {
        ( _content as FrameworkElement ).AddHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ), true );
      }
      else
      {
        this.AddHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ), true );
      }

      // update the Visual property of the view finder display panel's VisualBrush
      if( _viewFinderDisplay != null && _viewFinderDisplay.VisualBrush != null )
      {
        _viewFinderDisplay.VisualBrush.Visual = _content;
      }
    }

    protected override void OnGotKeyboardFocus( KeyboardFocusChangedEventArgs e )
    {
      this.MonitorInput();
      base.OnGotKeyboardFocus( e );
    }

    protected override void OnLostKeyboardFocus( KeyboardFocusChangedEventArgs e )
    {
      this.MonitorInput();
      base.OnLostKeyboardFocus( e );
    }

    protected override void OnInitialized( EventArgs e )
    {
      base.OnInitialized( e );
      this.CoerceValue( Zoombox.IsAnimatedProperty );
    }

    protected override void OnRender( DrawingContext drawingContext )
    {
      if( this.HasArrangedContentPresenter && !this.HasRenderedFirstView )
      {
        this.HasRenderedFirstView = true;
        if( this.RefocusViewOnFirstRender )
        {
          this.RefocusViewOnFirstRender = false;
          bool oldAnimated = this.IsAnimated;
          this.IsAnimated = false;
          try
          {
            this.RefocusView();
          }
          finally
          {
            this.IsAnimated = oldAnimated;
          }
        }
      }

      base.OnRender( drawingContext );
    }

    private static void RefocusView( DependencyObject o, DependencyPropertyChangedEventArgs e )
    {
      Zoombox zoombox = o as Zoombox;
      zoombox.UpdateView( zoombox.CurrentView, true, false, zoombox.ViewStackIndex );
    }

    private void AttachToVisualTree()
    {
      // detach from the old tree
      this.DetachFromVisualTree();

      // create the drag adorner for selection operations
      _dragAdorner = new DragAdorner( this );

      // check the template for a SelectionBrush resource
      if( this.Template.Resources.Contains( "SelectionBrush" ) )
        _dragAdorner.Brush = this.Template.Resources[ "SelectionBrush" ] as Brush;

      // check the template for a SelectionPen resource
      if( this.Template.Resources.Contains( "SelectionPen" ) )
        _dragAdorner.Pen = this.Template.Resources[ "SelectionPen" ] as Pen;

      // check the template for key bindings
      if( this.Template.Resources.Contains( "InputBindings" ) )
      {
        InputBindingCollection inputBindings = this.Template.Resources[ "InputBindings" ] as InputBindingCollection;
        if( inputBindings != null )
        {
          this.InputBindings.AddRange( inputBindings );
        }
      }

      // locate the content presenter
      _contentPresenter = VisualTreeHelperEx.FindDescendantByType( this, typeof( ContentPresenter ) ) as ContentPresenter;
      if( _contentPresenter == null )
        throw new InvalidTemplateException( ErrorMessages.GetMessage( "ZoomboxTemplateNeedsContent" ) );

      //locate the vertical scrollBar
      if( _verticalScrollBar != null )
      {
        _verticalScrollBar.Scroll -= this.VerticalScrollBar_Scroll;
      }

      _verticalScrollBar = this.GetTemplateChild( PART_VerticalScrollBar ) as ScrollBar;
      if( _verticalScrollBar == null )
        throw new InvalidTemplateException( ErrorMessages.GetMessage( "Zoombox vertical scrollBar not found." ) );

      _verticalScrollBar.Scroll += this.VerticalScrollBar_Scroll;

      //locate the horizontal scrollBar
      if( _horizontalScrollBar != null )
      {
        _horizontalScrollBar.Scroll -= this.HorizontalScrollBar_Scroll;
      }

      _horizontalScrollBar = this.GetTemplateChild( PART_HorizontalScrollBar ) as ScrollBar;
      if( _horizontalScrollBar == null )
        throw new InvalidTemplateException( ErrorMessages.GetMessage( "Zoombox horizontal scrollBar not found." ) );

      _horizontalScrollBar.Scroll += this.HorizontalScrollBar_Scroll;

      // check the template for an AdornerDecorator
      AdornerLayer al = null;
      AdornerDecorator ad = VisualTreeHelperEx.FindDescendantByType( this, typeof( AdornerDecorator ) ) as AdornerDecorator;
      if( ad != null )
      {
        al = ad.AdornerLayer;
      }
      else
      {
        // look for an inherited adorner layer
        try
        {
          al = AdornerLayer.GetAdornerLayer( this );
        }
        catch( Exception )
        {
        }
      }

      // add the drag adorner to the adorner layer
      if( al != null )
      {
        al.Add( _dragAdorner );
      }

      // TODO: Why is it necessary to walk the visual tree the first time through?
      // If we don't do the following, the content is not laid out correctly (centered) initially.
      VisualTreeHelperEx.FindDescendantWithPropertyValue( this, Button.IsPressedProperty, true );

      // set a reference to the ViewFinder element, if present
      this.SetValue( Zoombox.ViewFinderPropertyKey, this.Template.FindName( "ViewFinder", this ) as FrameworkElement );

      // locate the view finder display panel
      _viewFinderDisplay = VisualTreeHelperEx.FindDescendantByType( this, typeof( ZoomboxViewFinderDisplay ) ) as ZoomboxViewFinderDisplay;

      // if a ViewFinder was specified but no display panel is present, throw an exception
      if( this.ViewFinder != null && _viewFinderDisplay == null )
        throw new InvalidTemplateException( ErrorMessages.GetMessage( "ZoomboxHasViewFinderButNotDisplay" ) );

      // set up the VisualBrush and adorner for the display panel
      if( _viewFinderDisplay != null )
      {
        // create VisualBrush for the view finder display panel
        this.CreateVisualBrushForViewFinder( _content );

        // hook up event handlers for dragging and resizing the viewport
        _viewFinderDisplay.MouseMove += new MouseEventHandler( this.ViewFinderDisplayMouseMove );
        _viewFinderDisplay.MouseLeftButtonDown += new MouseButtonEventHandler( this.ViewFinderDisplayBeginCapture );
        _viewFinderDisplay.MouseLeftButtonUp += new MouseButtonEventHandler( this.ViewFinderDisplayEndCapture );

        // bind the ViewportRect property of the display to the Viewport of the Zoombox
        Binding binding = new Binding( "Viewport" );
        binding.Mode = BindingMode.OneWay;
        binding.Converter = new ViewFinderSelectionConverter( this );
        binding.Source = this;
        _viewFinderDisplay.SetBinding( ZoomboxViewFinderDisplay.ViewportRectProperty, binding );
      }

      // set up event handler to run once the content presenter has been arranged
      _contentPresenter.LayoutUpdated += new EventHandler( this.ContentPresenterFirstArranged );
    }

    private void CreateVisualBrushForViewFinder( Visual visual )
    {
      _viewFinderDisplay.VisualBrush = new VisualBrush( visual );
      _viewFinderDisplay.VisualBrush.Stretch = Stretch.Uniform;
      _viewFinderDisplay.VisualBrush.AlignmentX = AlignmentX.Left;
      _viewFinderDisplay.VisualBrush.AlignmentY = AlignmentY.Top;
    }

    private void ContentPresenterFirstArranged( object sender, EventArgs e )
    {
      // remove the handler
      _contentPresenter.LayoutUpdated -= new EventHandler( this.ContentPresenterFirstArranged );

      // it's now safe to update the view
      this.HasArrangedContentPresenter = true;
      this.InvalidateVisual();

      // temporarily disable animations
      bool oldAnimated = IsAnimated;
      this.IsAnimated = false;
      try
      {
        // set the initial view
        double scale = this.Scale;
        Point position = this.Position;

        // if there are items in the ViewStack and a ViewStackIndex is set, use it
        if( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
        {
          bool isInitialViewSet = false;
          if( this.ViewStack.Count > 0 )
          {
            if( this.ViewStackIndex >= 0 )
            {
              if( this.ViewStackIndex > this.ViewStack.Count - 1 )
              {
                this.ViewStackIndex = this.ViewStack.Count - 1;
              }
              else
              {
                this.UpdateView( this.ViewStack[ this.ViewStackIndex ], false, false, this.ViewStackIndex );
              }
            }
            else if( this.EffectiveViewStackMode != ZoomboxViewStackMode.Auto )
            {
              // if this is not an auto-stack, then ensure the index is set to a valid value
              if( this.ViewStackIndex < 0 )
              {
                this.ViewStackIndex = 0;
              }
            }

            // if a ViewStackIndex has been set, apply the scale and position values, iff different than the default values
            if( this.ViewStackIndex >= 0 )
            {
              isInitialViewSet = true;
              if( !DoubleHelper.IsNaN( scale ) || !PointHelper.IsEmpty( position ) )
              {
                this.UpdateView( new ZoomboxView( scale, position ), false, false );
              }
            }
          }

          if( !isInitialViewSet )
          {
            // set initial view according to the scale and position values and push it on the stack, as necessary
            ZoomboxView initialView = new ZoomboxView( DoubleHelper.IsNaN( Scale ) ? 1.0 : Scale,
              PointHelper.IsEmpty( position ) ? new Point() : position );

            if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Auto )
            {
              this.ViewStack.PushView( initialView );
              this.ViewStackIndex = 0;
            }
            else
            {
              this.UpdateView( initialView, false, false );
            }
          }
        }
        else
        {
          // just set initial view according to the scale and position values
          ZoomboxView initialView = new ZoomboxView( DoubleHelper.IsNaN( Scale ) ? 1.0 : this.Scale, position );
          this.UpdateView( initialView, false, false );
        }
      }
      finally
      {
        IsAnimated = oldAnimated;
      }
    }

    private void DetachFromVisualTree()
    {
      // remove the drag adorner
      if( _dragAdorner != null )
        AdornerLayer.GetAdornerLayer( this ).Remove( _dragAdorner );

      // remove the layout updated handler, if present
      if( _contentPresenter != null )
      {
        _contentPresenter.LayoutUpdated -= new EventHandler( this.ContentPresenterFirstArranged );
      }

      // remove the view finder display panel's visual brush and adorner
      if( _viewFinderDisplay != null )
      {
        _viewFinderDisplay = null;
      }

      // set object references to null
      _contentPresenter = null;
    }

    private void Zoombox_Loaded( object sender, RoutedEventArgs e )
    {
      this.SetScrollBars();
    }

    private void VerticalScrollBar_Scroll( object sender, ScrollEventArgs e )
    {
      double diff = e.NewValue + _relativePosition.Y;
      this.OnDrag( new DragDeltaEventArgs( 0d, -diff / this.Scale ), false );

      // Raise the Scroll event to user
      EventHandler<ScrollEventArgs> handler = this.Scroll;
      if( handler != null )
      {
        handler( this, e );
      }
    }

    private void HorizontalScrollBar_Scroll( object sender, ScrollEventArgs e )
    {
      double diff = e.NewValue + _relativePosition.X;
      this.OnDrag( new DragDeltaEventArgs( -diff / this.Scale, 0d ), false );

      // Raise the Scroll event to user
      EventHandler<ScrollEventArgs> handler = this.Scroll;
      if( handler != null )
      {
        handler( this, e );
      }
    }

    private void DragDisplayViewport( DragDeltaEventArgs e, bool end )
    {
      // get the scale of the view finder display panel, the selection rect, and the VisualBrush rect
      double scale = _viewFinderDisplay.Scale;
      Rect viewportRect = _viewFinderDisplay.ViewportRect;
      Rect vbRect = _viewFinderDisplay.ContentBounds;

      // if the entire content is visible, do nothing
      if( viewportRect.Contains( vbRect ) )
        return;

      // ensure that we stay within the bounds of the VisualBrush
      double dx = e.HorizontalChange;
      double dy = e.VerticalChange;

      // check left boundary
      if( viewportRect.Left < vbRect.Left )
      {
        dx = Math.Max( 0, dx );
      }
      else if( viewportRect.Left + dx < vbRect.Left )
      {
        dx = vbRect.Left - viewportRect.Left;
      }

      // check right boundary
      if( viewportRect.Right > vbRect.Right )
      {
        dx = Math.Min( 0, dx );
      }
      else if( viewportRect.Right + dx > vbRect.Left + vbRect.Width )
      {
        dx = vbRect.Left + vbRect.Width - viewportRect.Right;
      }

      // check top boundary
      if( viewportRect.Top < vbRect.Top )
      {
        dy = Math.Max( 0, dy );
      }
      else if( viewportRect.Top + dy < vbRect.Top )
      {
        dy = vbRect.Top - viewportRect.Top;
      }

      // check bottom boundary
      if( viewportRect.Bottom > vbRect.Bottom )
      {
        dy = Math.Min( 0, dy );
      }
      else if( viewportRect.Bottom + dy > vbRect.Top + vbRect.Height )
      {
        dy = vbRect.Top + vbRect.Height - viewportRect.Bottom;
      }

      // call the main OnDrag handler that is used when dragging the content directly
      this.OnDrag( new DragDeltaEventArgs( -dx / scale / _viewboxFactor, -dy / scale / _viewboxFactor ), end );

      // for a drag operation, update the origin with each delta
      _originPoint = _originPoint + new Vector( dx, dy );
    }

    private void InitCommands()
    {
      CommandBinding binding = new CommandBinding( Zoombox.Back, this.GoBack, this.CanGoBack );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.Center, this.CenterContent );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.Fill, this.FillToBounds );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.Fit, this.FitToBounds );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.Forward, this.GoForward, this.CanGoForward );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.Home, this.GoHome, this.CanGoHome );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.PanDown, this.PanDownExecuted );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.PanLeft, this.PanLeftExecuted );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.PanRight, this.PanRightExecuted );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.PanUp, this.PanUpExecuted );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.Refocus, this.RefocusView, this.CanRefocusView );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.ZoomIn, this.ZoomInExecuted );
      this.CommandBindings.Add( binding );

      binding = new CommandBinding( Zoombox.ZoomOut, this.ZoomOutExecuted );
      this.CommandBindings.Add( binding );
    }

    private void MonitorInput()
    {
      // cannot pre-process input in partial trust
      if( this.HasUIPermission )
      {
        this.PreProcessInput();
      }
    }

    private void OnContentSizeChanged( object sender, SizeChangedEventArgs e )
    {
      this.UpdateViewFinderDisplayContentBounds();

      if( this.HasArrangedContentPresenter )
      {
        if( this.HasRenderedFirstView )
        {
          this.SetScrollBars();
          this.UpdateView( this.CurrentView, true, false, this.CurrentViewIndex );
        }
        else
        {
          // if the content size changes after the content presenter has been arranged,
          // but before the first view is rendered, invalidate the render so we can refocus 
          // the view on the first render
          this.RefocusViewOnFirstRender = true;
          this.InvalidateVisual();
        }
      }
    }

    private void OnDrag( DragDeltaEventArgs e, bool end )
    {
      Point relativePosition = _relativePosition;
      double scale = this.Scale;
      Point newPosition = relativePosition + ( this.ContentOffset * scale ) + new Vector( e.HorizontalChange * scale, e.VerticalChange * scale );

      // update the transform
      this.UpdateView( new ZoomboxView( scale, newPosition ), false, end );
    }

    private void OnLayoutUpdated( object sender, EventArgs e )
    {
      this.UpdateViewport();
    }

    private void OnSelectRegion( DragDeltaEventArgs e, bool end )
    {
      // draw adorner rect
      if( end )
      {
        _dragAdorner.Rect = Rect.Empty;

        if( _trueContent != null )
        {
          // get the selected region (in the content's coordinate space) based on the adorner's last position and size
          Rect selection =
            new Rect(
              this.TranslatePoint( _dragAdorner.LastPosition, _trueContent ),
              this.TranslatePoint( _dragAdorner.LastPosition + new Vector( _dragAdorner.LastSize.Width, _dragAdorner.LastSize.Height ), _trueContent ) );

          // zoom to the selection
          this.ZoomTo( selection );
        }
      }
      else
      {
        _dragAdorner.Rect =
          Rect.Intersect(
            new Rect(
              _originPoint,
              new Vector( e.HorizontalChange, e.VerticalChange ) ),
            new Rect(
              new Point( 0, 0 ),
              new Point( this.RenderSize.Width, this.RenderSize.Height ) ) );
      }
    }

    private void OnSizeChanged( object sender, SizeChangedEventArgs e )
    {
      if( !this.HasArrangedContentPresenter )
        return;

      this.SetScrollBars();

      // when the size is changing, the viewbox factor must be updated before updating the view
      this.UpdateViewboxFactor();

      bool oldIsAnimated = this.IsAnimated;
      this.IsAnimated = false;
      try
      {
        this.UpdateView( this.CurrentView, false, false, this.ViewStackIndex );
      }
      finally
      {
        this.IsAnimated = oldIsAnimated;
      }
    }

    private void SetScrollBars()
    {
      if( _content == null || _verticalScrollBar == null || _horizontalScrollBar == null )
        return;

      var contentSize = ( _content is Viewbox ) ? ( ( Viewbox )_content ).Child.DesiredSize : this.RenderSize;

      _verticalScrollBar.SmallChange = 10d;
      _verticalScrollBar.LargeChange = 10d;
      _verticalScrollBar.Minimum = 0d;
      _verticalScrollBar.ViewportSize = this.RenderSize.Height;
      _verticalScrollBar.Maximum = contentSize.Height - _verticalScrollBar.ViewportSize;

      _horizontalScrollBar.SmallChange = 10d;
      _horizontalScrollBar.LargeChange = 10d;
      _horizontalScrollBar.Minimum = 0d;
      _horizontalScrollBar.ViewportSize = this.RenderSize.Width;
      _horizontalScrollBar.Maximum = contentSize.Width - _horizontalScrollBar.ViewportSize;
    }

    private void PreProcessInput()
    {
      // if mouse is over the Zoombox element or if it has keyboard focus, pre-process input 
      // to update the KeyModifier trigger properties (e.g., DragModifiersAreActive)
      if( this.IsMouseOver || this.IsKeyboardFocusWithin )
      {
        if( !this.IsMonitoringInput )
        {
          this.IsMonitoringInput = true;
          InputManager.Current.PreNotifyInput += new NotifyInputEventHandler( this.PreProcessInput );
          this.UpdateKeyModifierTriggerProperties();
        }
      }
      else
      {
        if( this.IsMonitoringInput )
        {
          this.IsMonitoringInput = false;
          InputManager.Current.PreNotifyInput -= new NotifyInputEventHandler( this.PreProcessInput );

          this.SetAreDragModifiersActive( false );
          this.SetAreRelativeZoomModifiersActive( false );
          this.SetAreZoomModifiersActive( false );
          this.SetAreZoomToSelectionModifiersActive( false );
        }
      }
    }

    private void PreProcessInput( object sender, NotifyInputEventArgs e )
    {
      if( e.StagingItem.Input is KeyEventArgs )
      {
        this.UpdateKeyModifierTriggerProperties();
      }
    }

    private void ProcessMouseLeftButtonDown( MouseButtonEventArgs e )
    {
      if( this.ZoomToSelectionModifiers.AreActive )
      {
        this.SetIsDraggingContent( false );
        this.SetIsSelectingRegion( true );
      }
      else if( this.DragModifiers.AreActive )
      {
        this.SetIsSelectingRegion( false );
        this.SetIsDraggingContent( true );
      }
      else
      {
        this.SetIsSelectingRegion( false );
        this.SetIsDraggingContent( false );
      }

      // if nothing to do, just return
      if( !this.IsSelectingRegion && !this.IsDraggingContent )
        return;

      // set the origin point and capture the mouse
      _originPoint = e.GetPosition( this );
      _contentPresenter.CaptureMouse();
      e.Handled = true;
      if( this.IsDraggingContent )
      {
        // execute the Drag operation
        this.OnDrag( new DragDeltaEventArgs( 0, 0 ), false );
      }
      else if( this.IsSelectingRegion )
      {
        this.OnSelectRegion( new DragDeltaEventArgs( 0, 0 ), false );
      }
    }

    private void ProcessMouseLeftButtonUp( MouseButtonEventArgs e )
    {
      if( !this.IsDraggingContent && !this.IsSelectingRegion )
        return;

      bool endDrag = this.IsDraggingContent;

      this.SetIsDraggingContent( false );
      this.SetIsSelectingRegion( false );

      _originPoint = new Point();
      _contentPresenter.ReleaseMouseCapture();
      e.Handled = true;

      if( endDrag )
      {
        this.OnDrag( new DragDeltaEventArgs( 0, 0 ), true );
      }
      else
      {
        this.OnSelectRegion( new DragDeltaEventArgs( 0, 0 ), true );
      }
    }

    private void ProcessMouseMove( MouseEventArgs e )
    {
      if( e.MouseDevice.LeftButton != MouseButtonState.Pressed )
        return;

      if( !this.IsDraggingContent && !this.IsSelectingRegion )
        return;

      Point pos = e.GetPosition( this );
      e.Handled = true;

      if( this.IsDraggingContent )
      {
        Vector delta = ( pos - _originPoint ) / this.Scale;
        this.OnDrag( new DragDeltaEventArgs( delta.X, delta.Y ), false );
        _originPoint = pos;
      }
      else if( this.IsSelectingRegion )
      {
        Vector delta = pos - _originPoint;
        this.OnSelectRegion( new DragDeltaEventArgs( delta.X, delta.Y ), false );
      }
    }

    private void ProcessMouseWheelZoom( MouseWheelEventArgs e )
    {
      if( _content == null )
        return;

      // check modifiers to see if there's work to do
      bool doZoom = this.ZoomModifiers.AreActive;
      bool doRelativeZoom = this.RelativeZoomModifiers.AreActive;

      // can't do both, so assume relative zoom
      if( doZoom && doRelativeZoom )
      {
        doZoom = false;
      }

      if( !( doZoom || doRelativeZoom ) )
        return;

      e.Handled = true;
      double percentage = ( ( e.Delta / Zoombox.MOUSE_WHEEL_DELTA ) * this.ZoomPercentage ) / 100;

      // Are we doing a zoom relative to the current mouse position?
      if( doRelativeZoom )
      {
        this.Zoom( percentage, Mouse.GetPosition( _content ) );
      }
      else
      {
        this.Zoom( percentage );
      }
    }

    private void ProcessNavigationButton( RoutedEventArgs e )
    {
      if( e is MouseButtonEventArgs )
      {
        MouseButtonEventArgs mbea = e as MouseButtonEventArgs;
        if( mbea.ChangedButton == MouseButton.XButton1
            || mbea.ChangedButton == MouseButton.XButton2 )
        {
          if( mbea.ChangedButton == MouseButton.XButton2 )
          {
            this.GoForward();
          }
          else
          {
            this.GoBack();
          }
          mbea.Handled = true;
        }
      }
      else if( e is KeyEventArgs )
      {
        KeyEventArgs kea = e as KeyEventArgs;
        if( kea.Key == Key.Back || kea.Key == Key.BrowserBack || kea.Key == Key.BrowserForward )
        {
          if( kea.Key == Key.BrowserForward )
          {
            this.GoForward();
          }
          else
          {
            this.GoBack();
          }
          kea.Handled = true;
        }
      }
    }

    private void ResizeDisplayViewport( DragDeltaEventArgs e, ResizeEdge relativeTo )
    {
      // get the existing viewport rect and scale
      Rect viewportRect = _viewFinderDisplay.ViewportRect;
      double scale = _viewFinderDisplay.Scale;

      // ensure that we stay within the bounds of the VisualBrush
      double x = Math.Max( _resizeViewportBounds.Left, Math.Min( _resizeDraggingPoint.X + e.HorizontalChange, _resizeViewportBounds.Right ) );
      double y = Math.Max( _resizeViewportBounds.Top, Math.Min( _resizeDraggingPoint.Y + e.VerticalChange, _resizeViewportBounds.Bottom ) );

      // get the selected region in the coordinate space of the content
      Point anchorPoint = new Point( _resizeAnchorPoint.X / scale, _resizeAnchorPoint.Y / scale );
      Vector newRegionVector = new Vector( ( x - _resizeAnchorPoint.X ) / scale / _viewboxFactor, ( y - _resizeAnchorPoint.Y ) / scale / _viewboxFactor );
      Rect region = new Rect( anchorPoint, newRegionVector );

      // now translate the region from the coordinate space of the content 
      // to the coordinate space of the content presenter
      region =
        new Rect(
          _content.TranslatePoint( region.TopLeft, _contentPresenter ),
          _content.TranslatePoint( region.BottomRight, _contentPresenter ) );

      // calculate actual scale value
      double aspectX = this.RenderSize.Width / region.Width;
      double aspectY = this.RenderSize.Height / region.Height;
      scale = aspectX < aspectY ? aspectX : aspectY;

      // scale relative to the anchor point
      this.ZoomTo( scale, anchorPoint, false, false );
    }

    private void UpdateKeyModifierTriggerProperties()
    {
      this.SetAreDragModifiersActive( this.DragModifiers.AreActive );
      this.SetAreRelativeZoomModifiersActive( this.RelativeZoomModifiers.AreActive );
      this.SetAreZoomModifiersActive( this.ZoomModifiers.AreActive );
      this.SetAreZoomToSelectionModifiersActive( this.ZoomToSelectionModifiers.AreActive );
    }

    private void UpdateView( ZoomboxView view, bool allowAnimation, bool allowStackAddition )
    {
      this.UpdateView( view, allowAnimation, allowStackAddition, -1 );
    }

    private void UpdateView( ZoomboxView view, bool allowAnimation, bool allowStackAddition, int stackIndex )
    {
      if( _contentPresenter == null || _content == null || !this.HasArrangedContentPresenter )
        return;

      // if an absolute view is being used and only a Scale value has been specified,
      // use the ZoomTo() function to perform a relative zoom
      if( view.ViewKind == ZoomboxViewKind.Absolute && PointHelper.IsEmpty( view.Position ) )
      {
        this.ZoomTo( view.Scale, allowStackAddition );
        return;
      }

      // disallow reentrancy
      if( !this.IsUpdatingView )
      {
        this.IsUpdatingView = true;
        try
        {
          // determine the new scale and position
          double newRelativeScale = _viewboxFactor;
          Point newRelativePosition = new Point();
          Rect region = Rect.Empty;
          switch( view.ViewKind )
          {
            case ZoomboxViewKind.Empty:
              break;

            case ZoomboxViewKind.Absolute:
              newRelativeScale = DoubleHelper.IsNaN( view.Scale ) ? _relativeScale : view.Scale;
              newRelativePosition = PointHelper.IsEmpty( view.Position ) ? _relativePosition
                  : new Point( view.Position.X, view.Position.Y ) - this.ContentOffset * newRelativeScale;
              break;

            case ZoomboxViewKind.Region:
              region = view.Region;
              break;

            case ZoomboxViewKind.Center:
              {
                // get the current ContentRect in the coordinate space of the Zoombox control
                Rect currentContentRect =
                  new Rect(
                    _content.TranslatePoint( this.ContentRect.TopLeft, this ),
                    _content.TranslatePoint( this.ContentRect.BottomRight, this ) );

                // inflate (or deflate) the rect by the appropriate amounts in the x & y directions
                region = Rect.Inflate( currentContentRect,
                    ( this.RenderSize.Width / _viewboxFactor - currentContentRect.Width ) / 2,
                    ( this.RenderSize.Height / _viewboxFactor - currentContentRect.Height ) / 2 );

                // now translate the centered rect back to the coordinate space of the content
                region = new Rect( this.TranslatePoint( region.TopLeft, _content ), this.TranslatePoint( region.BottomRight, _content ) );
              }
              break;

            case ZoomboxViewKind.Fit:
              region = this.ContentRect;
              break;

            case ZoomboxViewKind.Fill:
              region = this.CalculateFillRect();
              break;
          }

          if( view.ViewKind != ZoomboxViewKind.Empty )
          {
            if( !region.IsEmpty )
            {   // ZOOMING TO A REGION
              this.CalculatePositionAndScale( region, ref newRelativePosition, ref newRelativeScale );
            }
            else if( view != ZoomboxView.Empty )
            {   // USING ABSOLUTE POSITION AND SCALE VALUES

              // ensure that the scale value falls within the valid range
              if( newRelativeScale > MaxScale )
              {
                newRelativeScale = MaxScale;
              }
              else if( newRelativeScale < MinScale )
              {
                newRelativeScale = MinScale;
              }
            }

            double currentScale = _relativeScale;
            double currentX = _relativePosition.X;
            double currentY = _relativePosition.Y;

            ScaleTransform st = null;
            TranslateTransform tt = null;
            TransformGroup tg = null;

            if( _contentPresenter.RenderTransform != Transform.Identity )
            {
              tg = _contentPresenter.RenderTransform as TransformGroup;
              st = tg.Children[ 0 ] as ScaleTransform;
              tt = tg.Children[ 1 ] as TranslateTransform;
              currentScale = st.ScaleX;
              currentX = tt.X;
              currentY = tt.Y;
            }

            if( KeepContentInBounds == true )
            {
              Rect boundsRect = new Rect( new Size( this.ContentRect.Width * newRelativeScale, this.ContentRect.Height * newRelativeScale ) );

              // Calc viewport rect (should be inside bounds content rect)
              Point viewportPosition = new Point( -newRelativePosition.X, -newRelativePosition.Y );
              Rect viewportRect = new Rect( viewportPosition, _contentPresenter.RenderSize );

              if( DoubleHelper.AreVirtuallyEqual( _relativeScale, newRelativeScale ) ) // we are positioning the content, not scaling
              {
                // Handle the width and height seperately since the content extent 
                // could be contained only partially in the viewport.  Also if the viewport is only 
                // partially contained within the content extent.

                //
                // Content extent width is greater than the viewport's width (Zoomed in).  Make sure we restrict
                // the viewport X inside the content.
                //
                if( this.IsGreaterThanOrClose( boundsRect.Width, viewportRect.Width ) )
                {
                  if( boundsRect.Right < viewportRect.Right )
                  {
                    newRelativePosition.X = -( boundsRect.Width - viewportRect.Width );
                  }

                  if( boundsRect.Left > viewportRect.Left )
                  {
                    newRelativePosition.X = 0;
                  }
                }
                //
                // Viewport width is greater than the content extent's width (Zoomed out).  Make sure we restrict
                // the content X inside the viewport.
                //
                else if( this.IsGreaterThanOrClose( viewportRect.Width, boundsRect.Width ) )
                {
                  if( viewportRect.Right < boundsRect.Right )
                  {
                    newRelativePosition.X = viewportRect.Width - boundsRect.Width;
                  }

                  if( viewportRect.Left > boundsRect.Left )
                  {
                    newRelativePosition.X = 0;
                  }
                }

                //
                // Content extent height is greater than the viewport's height (Zoomed in).  Make sure we restrict
                // the viewport Y inside the content.
                //
                if( this.IsGreaterThanOrClose( boundsRect.Height, viewportRect.Height ) )
                {
                  if( boundsRect.Bottom < viewportRect.Bottom )
                  {
                    newRelativePosition.Y = -( boundsRect.Height - viewportRect.Height );
                  }

                  if( boundsRect.Top > viewportRect.Top )
                  {
                    newRelativePosition.Y = 0;
                  }
                }
                //
                // Viewport height is greater than the content extent's height (Zoomed out).  Make sure we restrict
                // the content Y inside the viewport.
                //
                else if( this.IsGreaterThanOrClose( viewportRect.Height, boundsRect.Height ) )
                {
                  if( viewportRect.Bottom < boundsRect.Bottom )
                  {
                    newRelativePosition.Y = viewportRect.Height - boundsRect.Height;
                  }

                  if( viewportRect.Top > boundsRect.Top )
                  {
                    newRelativePosition.Y = 0;
                  }
                }
              }
            }

            st = new ScaleTransform( newRelativeScale / _viewboxFactor, newRelativeScale / _viewboxFactor );
            tt = new TranslateTransform( newRelativePosition.X, newRelativePosition.Y );
            tg = new TransformGroup();
            tg.Children.Add( st );
            tg.Children.Add( tt );

            _contentPresenter.RenderTransform = tg;

            var initialContentSize = ( _content is Viewbox ) ? ( ( Viewbox )_content ).Child.DesiredSize : this.RenderSize;
            var scaledContentSize = new Size( initialContentSize.Width * newRelativeScale, initialContentSize.Height * newRelativeScale );

            if( allowAnimation && IsAnimated )
            {
              DoubleAnimation daScale = new DoubleAnimation( currentScale, newRelativeScale / _viewboxFactor, AnimationDuration );
              daScale.AccelerationRatio = this.AnimationAccelerationRatio;
              daScale.DecelerationRatio = this.AnimationDecelerationRatio;

              DoubleAnimation daTranslateX = new DoubleAnimation( currentX, newRelativePosition.X, AnimationDuration );
              daTranslateX.AccelerationRatio = this.AnimationAccelerationRatio;
              daTranslateX.DecelerationRatio = this.AnimationDecelerationRatio;

              DoubleAnimation daTranslateY = new DoubleAnimation( currentY, newRelativePosition.Y, AnimationDuration );
              daTranslateY.AccelerationRatio = this.AnimationAccelerationRatio;
              daTranslateY.DecelerationRatio = this.AnimationDecelerationRatio;
              daTranslateY.CurrentTimeInvalidated += new EventHandler( this.UpdateViewport );
              daTranslateY.CurrentStateInvalidated += new EventHandler( this.ZoomAnimationCompleted );

              // raise animation beginning event before beginning the animations
              RaiseEvent( new RoutedEventArgs( AnimationBeginningEvent, this ) );

              st.BeginAnimation( ScaleTransform.ScaleXProperty, daScale );
              st.BeginAnimation( ScaleTransform.ScaleYProperty, daScale );
              tt.BeginAnimation( TranslateTransform.XProperty, daTranslateX );
              tt.BeginAnimation( TranslateTransform.YProperty, daTranslateY );

              if( this.IsUsingScrollBars )
              {
                //Vertical scrollBar animations
                DoubleAnimation verticalMaxAnimation = new DoubleAnimation();
                verticalMaxAnimation.From = _verticalScrollBar.Maximum;
                verticalMaxAnimation.To = scaledContentSize.Height - _verticalScrollBar.ViewportSize;
                verticalMaxAnimation.Duration = AnimationDuration;
                _verticalScrollBar.BeginAnimation( ScrollBar.MaximumProperty, verticalMaxAnimation );

                DoubleAnimation verticalValueAnimation = new DoubleAnimation();
                verticalValueAnimation.From = _verticalScrollBar.Value;
                verticalValueAnimation.To = -newRelativePosition.Y;
                verticalValueAnimation.Duration = AnimationDuration;
                verticalValueAnimation.Completed += this.VerticalValueAnimation_Completed;
                _verticalScrollBar.BeginAnimation( ScrollBar.ValueProperty, verticalValueAnimation );

                //Horizontal scrollBar animations
                DoubleAnimation horizontalMaxAnimation = new DoubleAnimation();
                horizontalMaxAnimation.From = _horizontalScrollBar.Maximum;
                horizontalMaxAnimation.To = scaledContentSize.Width - _horizontalScrollBar.ViewportSize;
                horizontalMaxAnimation.Duration = AnimationDuration;
                _horizontalScrollBar.BeginAnimation( ScrollBar.MaximumProperty, horizontalMaxAnimation );

                DoubleAnimation horizontalValueAnimation = new DoubleAnimation();
                horizontalValueAnimation.From = _horizontalScrollBar.Value;
                horizontalValueAnimation.To = -newRelativePosition.X;
                horizontalValueAnimation.Duration = AnimationDuration;
                horizontalValueAnimation.Completed += this.HorizontalValueAnimation_Completed;
                _horizontalScrollBar.BeginAnimation( ScrollBar.ValueProperty, horizontalValueAnimation );
              }
            }
            else if( this.IsUsingScrollBars )
            {
              //Vertical scrollBar
              _verticalScrollBar.Maximum = scaledContentSize.Height - _verticalScrollBar.ViewportSize;
              _verticalScrollBar.Value = -newRelativePosition.Y;
              //Horizontal scrollBar
              _horizontalScrollBar.Maximum = scaledContentSize.Width - _horizontalScrollBar.ViewportSize;
              _horizontalScrollBar.Value = -newRelativePosition.X;
            }

            // maintain the relative scale and position for dragging and animating purposes
            _relativePosition = newRelativePosition;
            _relativeScale = newRelativeScale;

            // update the Scale and Position properties to keep them in sync with the current view
            this.Scale = newRelativeScale;
            _basePosition = newRelativePosition + this.ContentOffset * newRelativeScale;
            this.UpdateViewport();
          }

          // add the current view to the view stack
          if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Auto && allowStackAddition )
          {
            // if the last view was pushed on the stack within the last 300 milliseconds, discard it
            // since proximally close views are probably the result of a mouse wheel scroll
            if( this.ViewStack.Count > 1
              && Math.Abs( DateTime.Now.Ticks - _lastStackAddition.Ticks ) < TimeSpan.FromMilliseconds( 300 ).Ticks )
            {
              this.ViewStack.RemoveAt( this.ViewStack.Count - 1 );
              _lastStackAddition = DateTime.Now - TimeSpan.FromMilliseconds( 300 );
            }

            // if the current view is the same as the last view, do nothing
            if( this.ViewStack.Count > 0 && view == this.ViewStack.SelectedView )
            {
              // do nothing
            }
            else
            {
              // otherwise, push the current view on stack
              this.ViewStack.PushView( view );
              this.ViewStackIndex++;
              stackIndex = this.ViewStackIndex;

              // update the timestamp for the last stack addition
              _lastStackAddition = DateTime.Now;
            }
          }

          // update the stack indices used by CurrentViewChanged event
          _lastViewIndex = this.CurrentViewIndex;
          this.SetCurrentViewIndex( stackIndex );

          // set the new view parameters
          // NOTE: this is the only place where the CurrentView member should be set
          this.SetCurrentView( view );
        }
        finally
        {
          this.IsUpdatingView = false;
        }
      }
    }

    private bool IsGreaterThanOrClose( double value1, double value2 )
    {
      return value1 <= value2 ? DoubleHelper.AreVirtuallyEqual( value1, value2 ) : true;
    }

    private Rect CalculateFillRect()
    {
      // determine the x-y ratio of the current Viewport
      double xyRatio = this.RenderSize.Width / this.RenderSize.Height;

      // now find the maximal rect within the ContentRect that has the same ratio
      double x = 0;
      double y = 0;
      double width = this.ContentRect.Width;
      double height = this.ContentRect.Height;
      if( xyRatio > width / height )
      {
        height = width / xyRatio;
        y = ( this.ContentRect.Height - height ) / 2;
      }
      else
      {
        width = height * xyRatio;
        x = ( this.ContentRect.Width - width ) / 2;
      }

      return new Rect( x, y, width, height );
    }

    private void CalculatePositionAndScale( Rect region, ref Point newRelativePosition, ref double newRelativeScale )
    {
      // if there is nothing to scale, just return
      // if the region has no area, just return
      if( region.Width == 0 || region.Height == 0 )
        return;

      // verify that the selected region intersects with the content, which prevents 
      // the scale operation from zooming the content out of the current view
      if( !this.ContentRect.IntersectsWith( region ) )
        return;

      // translate the region from the coordinate space of the content 
      // to the coordinate space of the content presenter
      region =
        new Rect(
          _content.TranslatePoint( region.TopLeft, _contentPresenter ),
          _content.TranslatePoint( region.BottomRight, _contentPresenter ) );

      // calculate actual zoom, which must fit the entire selection 
      // while maintaining a 1:1 ratio
      double aspectX = this.RenderSize.Width / region.Width;
      double aspectY = this.RenderSize.Height / region.Height;
      newRelativeScale = aspectX < aspectY ? aspectX : aspectY;

      // ensure that the scale value falls within the valid range
      if( newRelativeScale > this.MaxScale )
      {
        newRelativeScale = this.MaxScale;
      }
      else if( newRelativeScale < this.MinScale )
      {
        newRelativeScale = this.MinScale;
      }

      // determine the new content position for this zoom operation based 
      // on HorizontalContentAlignment and VerticalContentAlignment
      double horizontalOffset = 0;
      double verticalOffset = 0;
      switch( this.HorizontalContentAlignment )
      {
        case HorizontalAlignment.Center:
        case HorizontalAlignment.Stretch:
          horizontalOffset = ( this.RenderSize.Width - region.Width * newRelativeScale ) / 2;
          break;

        case HorizontalAlignment.Right:
          horizontalOffset = ( this.RenderSize.Width - region.Width * newRelativeScale );
          break;
      }
      switch( VerticalContentAlignment )
      {
        case VerticalAlignment.Center:
        case VerticalAlignment.Stretch:
          verticalOffset = ( this.RenderSize.Height - region.Height * newRelativeScale ) / 2;
          break;

        case VerticalAlignment.Bottom:
          verticalOffset = ( this.RenderSize.Height - region.Height * newRelativeScale );
          break;
      }
      newRelativePosition =
        new Point( -region.TopLeft.X * newRelativeScale, -region.TopLeft.Y * newRelativeScale )
          + new Vector( horizontalOffset, verticalOffset );
    }

    private void UpdateViewFinderDisplayContentBounds()
    {
      if( _content == null || _trueContent == null || _viewFinderDisplay == null )
        return;

      this.UpdateViewboxFactor();

      // ensure the display panel has a size
      Size contentSize = _content.RenderSize;
      Size viewFinderSize = _viewFinderDisplay.AvailableSize;
      if( viewFinderSize.Width > 0d && DoubleHelper.AreVirtuallyEqual( viewFinderSize.Height, 0d ) )
      {
        // update height to accomodate width, while keeping a ratio equal to the actual content
        viewFinderSize = new Size( viewFinderSize.Width, contentSize.Height * viewFinderSize.Width / contentSize.Width );
      }
      else if( viewFinderSize.Height > 0d && DoubleHelper.AreVirtuallyEqual( viewFinderSize.Width, 0d ) )
      {
        // update width to accomodate height, while keeping a ratio equal to the actual content
        viewFinderSize = new Size( contentSize.Width * viewFinderSize.Height / contentSize.Height, viewFinderSize.Width );
      }

      // determine the scale of the view finder display panel
      double aspectX = viewFinderSize.Width / contentSize.Width;
      double aspectY = viewFinderSize.Height / contentSize.Height;
      double scale = aspectX < aspectY ? aspectX : aspectY;

      // determine the rect of the VisualBrush
      double vbWidth = contentSize.Width * scale;
      double vbHeight = contentSize.Height * scale;

      // set the ContentBounds and Scale properties on the view finder display panel
      _viewFinderDisplay.Scale = scale;
      _viewFinderDisplay.ContentBounds = new Rect( new Size( vbWidth, vbHeight ) );
    }

    private void UpdateViewboxFactor()
    {
      if( _content == null || _trueContent == null )
        return;

      double contentWidth = _content.RenderSize.Width;
      double trueContentWidth = _trueContent.RenderSize.Width;

      if( DoubleHelper.AreVirtuallyEqual( contentWidth, 0d ) || DoubleHelper.AreVirtuallyEqual( trueContentWidth, 0d ) )
      {
        _viewboxFactor = 1d;
      }
      else
      {
        _viewboxFactor = contentWidth / trueContentWidth;
      }
    }

    private void UpdateViewport()
    {
      // if we haven't attached to the visual tree yet or we don't have content, just return
      if( _contentPresenter == null || _trueContent == null )
        return;

      this.IsUpdatingViewport = true;
      try
      {
        // calculate the current viewport
        Rect viewport =
          new Rect(
            this.TranslatePoint( new Point( 0d, 0d ), _trueContent ),
            this.TranslatePoint( new Point( RenderSize.Width, RenderSize.Height ), _trueContent ) );

        // if the viewport has changed, set the Viewport dependency property
        if( !DoubleHelper.AreVirtuallyEqual( viewport, this.Viewport ) )
        {
          this.SetValue( Zoombox.ViewportPropertyKey, viewport );
        }
      }
      finally
      {
        this.IsUpdatingViewport = false;
      }
    }

    private void UpdateViewport( object sender, EventArgs e )
    {
      this.UpdateViewport();
    }

    private void ViewFinderDisplayBeginCapture( object sender, MouseButtonEventArgs e )
    {
      const double ARBITRARY_LARGE_VALUE = 10000000000;

      // if we need to acquire capture, the Tag property of the view finder display panel
      // will be a ResizeEdge value.
      if( _viewFinderDisplay.Tag is ResizeEdge )
      {
        // if the Tag is ResizeEdge.None, then its a drag operation; otherwise, its a resize
        if( ( ResizeEdge )_viewFinderDisplay.Tag == ResizeEdge.None )
        {
          this.IsDraggingViewport = true;
        }
        else
        {
          this.IsResizingViewport = true;
          Vector direction = new Vector();
          switch( ( ResizeEdge )_viewFinderDisplay.Tag )
          {
            case ResizeEdge.TopLeft:
              _resizeDraggingPoint = _viewFinderDisplay.ViewportRect.TopLeft;
              _resizeAnchorPoint = _viewFinderDisplay.ViewportRect.BottomRight;
              direction = new Vector( -1, -1 );
              break;

            case ResizeEdge.TopRight:
              _resizeDraggingPoint = _viewFinderDisplay.ViewportRect.TopRight;
              _resizeAnchorPoint = _viewFinderDisplay.ViewportRect.BottomLeft;
              direction = new Vector( 1, -1 );
              break;

            case ResizeEdge.BottomLeft:
              _resizeDraggingPoint = _viewFinderDisplay.ViewportRect.BottomLeft;
              _resizeAnchorPoint = _viewFinderDisplay.ViewportRect.TopRight;
              direction = new Vector( -1, 1 );
              break;

            case ResizeEdge.BottomRight:
              _resizeDraggingPoint = _viewFinderDisplay.ViewportRect.BottomRight;
              _resizeAnchorPoint = _viewFinderDisplay.ViewportRect.TopLeft;
              direction = new Vector( 1, 1 );
              break;
            case ResizeEdge.Left:
              _resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Left,
                  _viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
              _resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Right,
                  _viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
              direction = new Vector( -1, 0 );
              break;
            case ResizeEdge.Top:
              _resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
                  _viewFinderDisplay.ViewportRect.Top );
              _resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
                  _viewFinderDisplay.ViewportRect.Bottom );
              direction = new Vector( 0, -1 );
              break;
            case ResizeEdge.Right:
              _resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Right,
                  _viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
              _resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Left,
                  _viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
              direction = new Vector( 1, 0 );
              break;
            case ResizeEdge.Bottom:
              _resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
                  _viewFinderDisplay.ViewportRect.Bottom );
              _resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
                  _viewFinderDisplay.ViewportRect.Top );
              direction = new Vector( 0, 1 );
              break;
          }
          double scale = _viewFinderDisplay.Scale;
          Rect contentRect = _viewFinderDisplay.ContentBounds;
          Vector minVector = new Vector( direction.X * ARBITRARY_LARGE_VALUE, direction.Y * ARBITRARY_LARGE_VALUE );
          Vector maxVector = new Vector( direction.X * contentRect.Width / MaxScale, direction.Y * contentRect.Height / MaxScale );
          _resizeViewportBounds = new Rect( _resizeAnchorPoint + minVector, _resizeAnchorPoint + maxVector );
        }

        // store the origin of the operation and acquire capture
        _originPoint = e.GetPosition( _viewFinderDisplay );
        _viewFinderDisplay.CaptureMouse();
        e.Handled = true;
      }
    }

    private void ViewFinderDisplayEndCapture( object sender, MouseButtonEventArgs e )
    {
      // if a drag or resize is in progress, end it and release capture
      if( this.IsDraggingViewport || this.IsResizingViewport )
      {
        // call the DragDisplayViewport method to end the operation
        // and store the current position on the stack
        this.DragDisplayViewport( new DragDeltaEventArgs( 0, 0 ), true );

        // reset the dragging state variables and release capture
        this.IsDraggingViewport = false;
        this.IsResizingViewport = false;
        _originPoint = new Point();
        _viewFinderDisplay.ReleaseMouseCapture();
        e.Handled = true;
      }
    }

    private void ViewFinderDisplayMouseMove( object sender, MouseEventArgs e )
    {
      // if a drag operation is in progress, update the operation
      if( e.MouseDevice.LeftButton == MouseButtonState.Pressed
          && ( this.IsDraggingViewport || this.IsResizingViewport ) )
      {
        Point pos = e.GetPosition( _viewFinderDisplay );
        Vector delta = pos - _originPoint;
        if( this.IsDraggingViewport )
        {
          this.DragDisplayViewport( new DragDeltaEventArgs( delta.X, delta.Y ), false );
        }
        else
        {
          this.ResizeDisplayViewport( new DragDeltaEventArgs( delta.X, delta.Y ), ( ResizeEdge )_viewFinderDisplay.Tag );
        }
        e.Handled = true;
      }
      else
      {
        // update the cursor based on the nearest corner
        Point mousePos = e.GetPosition( _viewFinderDisplay );
        Rect viewportRect = _viewFinderDisplay.ViewportRect;
        double cornerDelta = viewportRect.Width * viewportRect.Height > 100 ? 5.0
            : Math.Sqrt( viewportRect.Width * viewportRect.Height ) / 2;

        // if the mouse is within the Rect and the Rect does not encompass the entire content, set the appropriate cursor
        if( viewportRect.Contains( mousePos )
            && !DoubleHelper.AreVirtuallyEqual( Rect.Intersect( viewportRect, _viewFinderDisplay.ContentBounds ), _viewFinderDisplay.ContentBounds ) )
        {
          if( PointHelper.DistanceBetween( mousePos, viewportRect.TopLeft ) < cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.TopLeft;
            _viewFinderDisplay.Cursor = Cursors.SizeNWSE;
          }
          else if( PointHelper.DistanceBetween( mousePos, viewportRect.BottomRight ) < cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.BottomRight;
            _viewFinderDisplay.Cursor = Cursors.SizeNWSE;
          }
          else if( PointHelper.DistanceBetween( mousePos, viewportRect.TopRight ) < cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.TopRight;
            _viewFinderDisplay.Cursor = Cursors.SizeNESW;
          }
          else if( PointHelper.DistanceBetween( mousePos, viewportRect.BottomLeft ) < cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.BottomLeft;
            _viewFinderDisplay.Cursor = Cursors.SizeNESW;
          }
          else if( mousePos.X <= viewportRect.Left + cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.Left;
            _viewFinderDisplay.Cursor = Cursors.SizeWE;
          }
          else if( mousePos.Y <= viewportRect.Top + cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.Top;
            _viewFinderDisplay.Cursor = Cursors.SizeNS;
          }
          else if( mousePos.X >= viewportRect.Right - cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.Right;
            _viewFinderDisplay.Cursor = Cursors.SizeWE;
          }
          else if( mousePos.Y >= viewportRect.Bottom - cornerDelta )
          {
            _viewFinderDisplay.Tag = ResizeEdge.Bottom;
            _viewFinderDisplay.Cursor = Cursors.SizeNS;
          }
          else
          {
            _viewFinderDisplay.Tag = ResizeEdge.None;
            _viewFinderDisplay.Cursor = Cursors.SizeAll;
          }
        }
        else
        {
          _viewFinderDisplay.Tag = null;
          _viewFinderDisplay.Cursor = Cursors.Arrow;
        }
      }
    }

    private void ZoomAnimationCompleted( object sender, EventArgs e )
    {
      if( ( sender as AnimationClock ).CurrentState != ClockState.Active )
      {
        // remove the event handlers
        ( sender as AnimationClock ).CurrentStateInvalidated -= new EventHandler( this.ZoomAnimationCompleted );
        ( sender as AnimationClock ).CurrentTimeInvalidated -= new EventHandler( this.UpdateViewport );

        // raise animation completed event
        this.RaiseEvent( new RoutedEventArgs( Zoombox.AnimationCompletedEvent, this ) );
      }
    }

    private void VerticalValueAnimation_Completed( object sender, EventArgs e )
    {
      //When the animaton is completed, with the FillBehavior to HoldEnd, 
      //the ScrollBarValue will be overriden with the final animation value, preventing future scroll.
      //To remove it use BeginAnimation with null.
      //http://msdn.microsoft.com/en-us/library/aa970493(v=vs.110).aspx      

      // only do this when all the overlapped animations are done or limits values reached.
      if( ( _verticalScrollBar.Value == -_relativePosition.Y )
        || ( _verticalScrollBar.Value == _verticalScrollBar.Maximum )
        || ( _verticalScrollBar.Value == _verticalScrollBar.Minimum ) )
      {
        var finalValue = _verticalScrollBar.Value;
        //this will reset Value to original value of animation
        _verticalScrollBar.BeginAnimation( ScrollBar.ValueProperty, null );
        //this will set to last value of the animation.
        _verticalScrollBar.Value = finalValue;
      }
    }

    private void HorizontalValueAnimation_Completed( object sender, EventArgs e )
    {
      //When the animaton is completed, with the FillBehavior to HoldEnd, 
      //the ScrollBarValue will be overriden with the final animation value, preventing future scroll.
      //To remove it use BeginAnimation with null.
      //http://msdn.microsoft.com/en-us/library/aa970493(v=vs.110).aspx      

      // only do this when all the overlapped animations are done or limits values reached.
      if( ( _horizontalScrollBar.Value == -_relativePosition.X )
        || ( _horizontalScrollBar.Value == _horizontalScrollBar.Maximum )
        || ( _horizontalScrollBar.Value == _horizontalScrollBar.Minimum ) )
      {
        var finalValue = _horizontalScrollBar.Value;
        //this will reset Value to original value of animation
        _horizontalScrollBar.BeginAnimation( ScrollBar.ValueProperty, null );
        //this will set to last value of the animation.
        _horizontalScrollBar.Value = finalValue;
      }
    }

    private void ZoomTo( double scale, bool allowStackAddition )
    {
      // if there is nothing to scale, just return
      if( _content == null )
        return;

      // adjust the current scale relative to the zoom origin
      this.ZoomTo( scale, this.GetZoomRelativePoint(), true, allowStackAddition );
    }

    private void ZoomTo( double scale, Point relativeTo, bool restrictRelativePointToContent, bool allowStackAddition )
    {
      // if there is nothing to scale, just return
      if( _content == null )
        return;

      if( double.IsNaN( scale ) )
        return;

      // if necessary, verify that the relativeTo point falls within the content
      if( restrictRelativePointToContent && !( new Rect( _content.RenderSize ) ).Contains( relativeTo ) )
        return;

      // ensure that the scale value falls within the valid range
      if( scale > this.MaxScale )
      {
        scale = this.MaxScale;
      }
      else if( scale < this.MinScale )
      {
        scale = this.MinScale;
      }

      // internally, updates are always relative to the Zoombox control
      Point translateFrom = relativeTo;
      if( this.HasRenderedFirstView )
      {
        // Note that this TranslatePoint approach will not work until the first render occurs
        relativeTo = _content.TranslatePoint( relativeTo, this );

        // adjust translateFrom based on relativeTo
        translateFrom = this.TranslatePoint( relativeTo, _contentPresenter );
      }
      else if( _contentPresenter != null )
      {
        // prior to the first render, just use the ContentPresenter's transform and do not adjust translateFrom
        if( _contentPresenter.RenderTransform == Transform.Identity )
        {
          // in order for this approach to work, we must at least make one pass to update a generic view
          // with Scale = 1.0 and Position = 0,0
          this.UpdateView( new ZoomboxView( 1, new Point( 0, 0 ) ), false, false );
        }

        // now there should be a valid RenderTransform
        relativeTo = ( _contentPresenter.RenderTransform as Transform ).Transform( relativeTo );
      }

      // determine the new content position for this zoom operation
      Point translateTo = new Point( relativeTo.X - ( translateFrom.X * scale / _viewboxFactor ),
          relativeTo.Y - ( translateFrom.Y * scale / _viewboxFactor ) )
          + this.ContentOffset * scale / _viewboxFactor;
      this.UpdateView( new ZoomboxView( scale, translateTo ), !this.IsResizingViewport, allowStackAddition );
    }

    private Point GetZoomRelativePoint()
    {
      Point zoomPoint;

      if( ZoomOn == ZoomboxZoomOn.View )
      {
        // Transform the viewport point to the content
        Point viewportZoomOrigin = new Point();

        viewportZoomOrigin.X = this.Viewport.X + ( this.Viewport.Width * this.ZoomOrigin.X );
        viewportZoomOrigin.Y = this.Viewport.Y + ( this.Viewport.Height * this.ZoomOrigin.Y );

        Point contentZoomOrigin = _trueContent.TranslatePoint( viewportZoomOrigin, _content );

        if( contentZoomOrigin.X < 0 )
        {
          contentZoomOrigin.X = 0;
        }
        else if( contentZoomOrigin.X > _content.RenderSize.Width )
        {
          contentZoomOrigin.X = _content.RenderSize.Width;
        }

        if( contentZoomOrigin.Y < 0 )
        {
          contentZoomOrigin.Y = 0;
        }
        else if( contentZoomOrigin.Y > _content.RenderSize.Height )
        {
          contentZoomOrigin.Y = _content.RenderSize.Height;
        }

        zoomPoint = contentZoomOrigin;
      }
      else
      {
        zoomPoint = new Point( _content.RenderSize.Width * ZoomOrigin.X, _content.RenderSize.Height * ZoomOrigin.Y );
      }

      return zoomPoint;
    }

    #region OnKeyDown Methods

    protected override void OnPreviewKeyDown( KeyEventArgs e )
    {
      if( this.NavigateOnPreview && !e.Handled )
      {
        this.ProcessNavigationButton( e );
      }

      base.OnPreviewKeyDown( e );
    }

    protected override void OnKeyDown( KeyEventArgs e )
    {
      if( !this.NavigateOnPreview && !e.Handled )
      {
        this.ProcessNavigationButton( e );
      }

      base.OnKeyDown( e );
    }

    #endregion

    #region OnMouseDown Methods

    protected override void OnPreviewMouseDown( MouseButtonEventArgs e )
    {
      if( this.NavigateOnPreview && !e.Handled )
      {
        this.ProcessNavigationButton( e );
      }

      base.OnPreviewMouseDown( e );
    }

    protected override void OnMouseDown( MouseButtonEventArgs e )
    {
      if( !this.NavigateOnPreview && !e.Handled )
      {
        this.ProcessNavigationButton( e );
      }

      base.OnMouseDown( e );
    }

    #endregion

    #region OnMouseEnter Methods

    protected override void OnMouseEnter( MouseEventArgs e )
    {
      this.MonitorInput();

      base.OnMouseEnter( e );
    }

    #endregion

    #region OnMouseLeave Methods

    protected override void OnMouseLeave( MouseEventArgs e )
    {
      this.MonitorInput();

      base.OnMouseLeave( e );
    }

    #endregion

    #region OnMouseLeftButton Methods

    protected override void OnPreviewMouseLeftButtonDown( MouseButtonEventArgs e )
    {
      if( this.DragOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseLeftButtonDown( e );
      }

      base.OnPreviewMouseLeftButtonDown( e );
    }

    protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
    {
      if( !this.DragOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseLeftButtonDown( e );
      }

      base.OnMouseLeftButtonDown( e );
    }

    protected override void OnPreviewMouseLeftButtonUp( MouseButtonEventArgs e )
    {
      if( this.DragOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseLeftButtonUp( e );
      }

      base.OnPreviewMouseLeftButtonUp( e );
    }

    protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e )
    {
      if( !this.DragOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseLeftButtonUp( e );
      }

      base.OnMouseLeftButtonUp( e );
    }

    #endregion

    #region OnMouseMove Methods

    protected override void OnPreviewMouseMove( MouseEventArgs e )
    {
      if( this.DragOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseMove( e );
      }

      base.OnPreviewMouseMove( e );
    }

    protected override void OnMouseMove( MouseEventArgs e )
    {
      if( !this.DragOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseMove( e );
      }

      base.OnMouseMove( e );
    }

    #endregion

    #region OnMouseWheel Methods

    protected override void OnPreviewMouseWheel( MouseWheelEventArgs e )
    {
      if( this.ZoomOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseWheelZoom( e );
      }

      base.OnPreviewMouseWheel( e );
    }

    protected override void OnMouseWheel( MouseWheelEventArgs e )
    {
      if( !this.ZoomOnPreview && !e.Handled && _contentPresenter != null )
      {
        this.ProcessMouseWheelZoom( e );
      }

      base.OnMouseWheel( e );
    }

    #endregion

    #region Private Fields

    // the default value for a single mouse wheel delta appears to be 28
    private static int MOUSE_WHEEL_DELTA = 28;

    // the content control's one and only content presenter
    private ContentPresenter _contentPresenter = null;

    //The Scrollbars
    private ScrollBar _verticalScrollBar = null;
    private ScrollBar _horizontalScrollBar = null;

    // the content of the Zoombox (cast as a UIElement)
    private UIElement _content = null;

    // the drag adorner used for selecting a region in a zoom-to-selection operation
    private DragAdorner _dragAdorner = null;

    // the view stack
    private ZoomboxViewStack _viewStack = null;

    // the view finder display panel
    // this is used to show the current viewport
    private ZoomboxViewFinderDisplay _viewFinderDisplay = null;

    // state variables used during drag and select operations
    private Rect _resizeViewportBounds = Rect.Empty;
    private Point _resizeAnchorPoint = new Point( 0, 0 );
    private Point _resizeDraggingPoint = new Point( 0, 0 );
    private Point _originPoint = new Point( 0, 0 );

    private double _viewboxFactor = 1.0;
    private double _relativeScale = 1.0;
    private Point _relativePosition = new Point();
    private Point _basePosition = new Point();

    // used to track the time delta between stack operations
    private DateTime _lastStackAddition;

    // used to provide stack index when view changes
    private int _lastViewIndex = -1;

    private BitVector32 _cacheBits = new BitVector32( 0 );

    #endregion

    #region ViewFinderSelectionConverter Nested Type

    private sealed class ViewFinderSelectionConverter : IValueConverter
    {
      public ViewFinderSelectionConverter( Zoombox zoombox )
      {
        _zoombox = zoombox;
      }

      public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
      {
        Rect viewport = ( Rect )value;
        if( viewport.IsEmpty )
          return viewport;

        // adjust the viewport from the coordinate space of the Content element
        // to the coordinate space of the view finder display panel
        double scale = _zoombox._viewFinderDisplay.Scale * _zoombox._viewboxFactor;
        Rect result = new Rect( viewport.Left * scale, viewport.Top * scale,
            viewport.Width * scale, viewport.Height * scale );
        result.Offset( _zoombox._viewFinderDisplay.ContentBounds.Left,
            _zoombox._viewFinderDisplay.ContentBounds.Top );
        return result;
      }

      public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
      {
        return null;
      }

      private readonly Zoombox _zoombox;
    }

    #endregion

    #region DragAdorner Nested Type

    internal sealed class DragAdorner : Adorner
    {
      public DragAdorner( UIElement adornedElement )
        : base( adornedElement )
      {
        this.ClipToBounds = true;
      }

      public static readonly DependencyProperty BrushProperty =
        DependencyProperty.Register( "Brush", typeof( Brush ), typeof( DragAdorner ),
          new FrameworkPropertyMetadata( Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender ) );

      public Brush Brush
      {
        get
        {
          return ( Brush )this.GetValue( DragAdorner.BrushProperty );
        }
        set
        {
          this.SetValue( DragAdorner.BrushProperty, value );
        }
      }

      public static readonly DependencyProperty PenProperty =
        DependencyProperty.Register( "Pen", typeof( Pen ), typeof( DragAdorner ),
          new FrameworkPropertyMetadata( new Pen( new SolidColorBrush( Color.FromArgb( 0x7F, 0x3F, 0x3F, 0x3F ) ), 2d ), FrameworkPropertyMetadataOptions.AffectsRender ) );

      public Pen Pen
      {
        get
        {
          return ( Pen )this.GetValue( DragAdorner.PenProperty );
        }
        set
        {
          this.SetValue( DragAdorner.PenProperty, value );
        }
      }

      public static readonly DependencyProperty RectProperty =
        DependencyProperty.Register( "Rect", typeof( Rect ), typeof( DragAdorner ),
          new FrameworkPropertyMetadata( Rect.Empty, FrameworkPropertyMetadataOptions.AffectsRender,
            new PropertyChangedCallback( DragAdorner.OnRectChanged ) ) );

      public Rect Rect
      {
        get
        {
          return ( Rect )this.GetValue( DragAdorner.RectProperty );
        }
        set
        {
          this.SetValue( DragAdorner.RectProperty, value );
        }
      }

      private static void OnRectChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
      {
        DragAdorner dragAdorner = ( DragAdorner )d;
        Rect rect = ( Rect )e.NewValue;

        // ignore empty values
        if( rect.IsEmpty )
          return;

        // if the value is not empty, cache the position and size
        dragAdorner._cachedPosition = ( ( Rect )e.NewValue ).TopLeft;
        dragAdorner._cachedSize = ( ( Rect )e.NewValue ).Size;
      }

      public Point LastPosition
      {
        get
        {
          return _cachedPosition;
        }
      }

      public Size LastSize
      {
        get
        {
          return _cachedSize;
        }
      }

      protected override void OnRender( DrawingContext drawingContext )
      {
        drawingContext.DrawRectangle( Brush, Pen, Rect );
      }

      private Point _cachedPosition;
      private Size _cachedSize;
    }

    #endregion

    #region CacheBits Nested Type

    private enum CacheBits
    {
      IsUpdatingView = 0x00000001,
      IsUpdatingViewport = 0x00000002,
      IsDraggingViewport = 0x00000004,
      IsResizingViewport = 0x00000008,
      IsMonitoringInput = 0x00000010,
      IsContentWrapped = 0x00000020,
      HasArrangedContentPresenter = 0x00000040,
      HasRenderedFirstView = 0x00000080,
      RefocusViewOnFirstRender = 0x00000100,
      HasUIPermission = 0x00000200,
    }

    #endregion

    #region ResizeEdge Nested Type

    private enum ResizeEdge
    {
      None,
      TopLeft,
      TopRight,
      BottomLeft,
      BottomRight,
      Left,
      Top,
      Right,
      Bottom,
    }

    #endregion
  }
}
