DocumentWorkspace.cs :  » GUI » Paint.net » PaintDotNet » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » GUI » Paint.net 
Paint.net » PaintDotNet » DocumentWorkspace.cs
/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors.     //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See src/Resources/Files/License.txt for full licensing and attribution      //
// details.                                                                    //
// .                                                                           //
/////////////////////////////////////////////////////////////////////////////////

using PaintDotNet.Data;
using PaintDotNet.Effects;
using PaintDotNet.HistoryFunctions;
using PaintDotNet.HistoryMementos;
using PaintDotNet.SystemLayer;
using PaintDotNet.Tools;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace PaintDotNet{
    /// <summary>
    /// Builds on DocumentView by adding application-specific elements.
    /// </summary>
    internal class DocumentWorkspace
        : DocumentView,
          IHistoryWorkspace,
          IThumbnailProvider
    {
        public static readonly DateTime NeverSavedDateTime = DateTime.MinValue;

        private static Type[] tools; // TODO: move to Tool class?
        private static ToolInfo[] toolInfos;

        private ZoomBasis zoomBasis;
        private AppWorkspace appWorkspace;
        private string filePath = null;
        private FileType fileType = null;
        private SaveConfigToken saveConfigToken = null;
        private Selection selection = new Selection();
        private Surface scratchSurface = null;
        private SelectionRenderer selectionRenderer;
        private Hashtable staticToolData = Hashtable.Synchronized(new Hashtable());
        private Tool activeTool;
        private Type previousActiveToolType;
        private Type preNullTool = null;
        private int nullToolCount = 0;
        private int zoomChangesCount = 0;
        private HistoryStack history;
        private Layer activeLayer;
        private System.Windows.Forms.Timer toolPulseTimer;
        private DateTime lastSaveTime = NeverSavedDateTime;
        private int suspendToolCursorChanges = 0;
        private ImageResource statusIcon = null;
        private string statusText = null;

        private readonly string contextStatusBarWithAngleFormat = PdnResources.GetString("StatusBar.Context.SelectedArea.Text.WithAngle.Format");
        private readonly string contextStatusBarFormat = PdnResources.GetString("StatusBar.Context.SelectedArea.Text.Format");

        public void SuspendToolCursorChanges()
        {
            ++this.suspendToolCursorChanges;
        }

        public void ResumeToolCursorChanges()
        {
            --this.suspendToolCursorChanges;

            if (this.suspendToolCursorChanges <= 0 && this.activeTool != null)
            {
                Cursor = this.activeTool.Cursor;
            }
        }
        
        public ImageResource StatusIcon
        {
            get
            {
                return this.statusIcon;
            }
        }

        public string StatusText
        {
            get
            {
                return this.statusText;
            }
        }

        public void SetStatus(string newStatusText, ImageResource newStatusIcon)
        {
            this.statusText = newStatusText;
            this.statusIcon = newStatusIcon;
            OnStatusChanged();
        }

        public event EventHandler StatusChanged;
        protected virtual void OnStatusChanged()
        {
            if (StatusChanged != null)
            {
                StatusChanged(this, EventArgs.Empty);
            }
        }

        static DocumentWorkspace()
        {
            InitializeTools();
            InitializeToolInfos();
        }

        public DateTime LastSaveTime
        {
            get
            {
                return this.lastSaveTime;
            }
        }

        public bool IsZoomChanging
        {
            get
            {
                return (this.zoomChangesCount > 0);
            }
        }

        private void BeginZoomChanges()
        {
            ++this.zoomChangesCount;
        }

        private void EndZoomChanges()
        {
            --this.zoomChangesCount;
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            PerformLayout();
            base.OnSizeChanged(e);
        }

        protected override void OnLayout(LayoutEventArgs e)
        {
            if (this.zoomBasis == ZoomBasis.FitToWindow)
            {
                ZoomToWindow();

                // This bizarre ordering of setting PanelAutoScroll prevents some very weird layout/scroll-without-scrollbars stuff.
                PanelAutoScroll = true;
                PanelAutoScroll = false;
            }

            base.OnLayout(e);
        }

        protected override void OnResize(EventArgs e)
        {
            if (this.zoomBasis == ZoomBasis.FitToWindow)
            {
                PerformLayout();
            }

            base.OnResize(e);
        }

        public DocumentWorkspace()
        {
            this.activeLayer = null;
            this.history = new HistoryStack(this);

            InitializeComponent();

            // hook the DocumentWorkspace with its selectedPath ...
            this.selectionRenderer = new SelectionRenderer(this.RendererList, this.Selection, this);
            this.RendererList.Add(this.selectionRenderer, true);
            this.selectionRenderer.EnableOutlineAnimation = true;
            this.selectionRenderer.EnableSelectionTinting = false;
            this.selectionRenderer.EnableSelectionOutline = true;

            this.selection.Changed += new EventHandler(Selection_Changed);

            this.zoomBasis = ZoomBasis.FitToWindow;
        }

        protected override void OnUnitsChanged()
        {
            if (!Selection.IsEmpty)
            {
                UpdateSelectionInfoInStatusBar();
            }

            base.OnUnitsChanged();
        }

        public void UpdateStatusBarToToolHelpText(Tool tool)
        {
            if (tool == null)
            {
                SetStatus(string.Empty, null);
            }
            else
            {
                string toolName = tool.Name;
                string helpText = tool.HelpText;

                string contextFormat = PdnResources.GetString("StatusBar.Context.Help.Text.Format");
                string contextText = string.Format(contextFormat, toolName, helpText);

                SetStatus(contextText, PdnResources.GetImageResource("Icons.MenuHelpHelpTopicsIcon.png"));
            }
        }

        public void UpdateStatusBarToToolHelpText()
        {
            UpdateStatusBarToToolHelpText(this.activeTool);
        }

        private void UpdateSelectionInfoInStatusBar()
        {
            if (Selection.IsEmpty)
            {
                UpdateStatusBarToToolHelpText();
            }
            else
            {
                string newStatusText;

                int area = 0;
                Rectangle bounds;

                using (PdnRegion tempSelection = Selection.CreateRegionRaw())
                {
                    tempSelection.Intersect(Document.Bounds);
                    bounds = Utility.GetRegionBounds(tempSelection);
                    area = tempSelection.GetArea();
                }

                string unitsAbbreviationXY;
                string xString;
                string yString;
                string unitsAbbreviationWH;
                string widthString;
                string heightString;

                Document.CoordinatesToStrings(Units, bounds.X, bounds.Y, out xString, out yString, out unitsAbbreviationXY);
                Document.CoordinatesToStrings(Units, bounds.Width, bounds.Height, out widthString, out heightString, out unitsAbbreviationWH);

                NumberFormatInfo nfi = (NumberFormatInfo)CultureInfo.CurrentCulture.NumberFormat.Clone();

                string areaString;
                if (this.Units == MeasurementUnit.Pixel)
                {
                    nfi.NumberDecimalDigits = 0;
                    areaString = area.ToString("N", nfi);
                }
                else
                {
                    nfi.NumberDecimalDigits = 2;
                    double areaD = Document.PixelAreaToPhysicalArea(area, this.Units);
                    areaString = areaD.ToString("N", nfi);
                }

                string pluralUnits = PdnResources.GetString("MeasurementUnit." + this.Units.ToString() + ".Plural");
                MoveToolBase moveTool = Tool as MoveToolBase;

                if (moveTool != null && moveTool.HostShouldShowAngle)
                {
                    NumberFormatInfo nfi2 = (NumberFormatInfo)nfi.Clone();
                    nfi2.NumberDecimalDigits = 2;
                    float angle = moveTool.HostAngle;

                    while (angle > 180.0f)
                    {
                        angle -= 360.0f;
                    }

                    while (angle < -180.0f)
                    {
                        angle += 360.0f;
                    }

                    newStatusText = string.Format(
                        contextStatusBarWithAngleFormat,
                        xString,
                        unitsAbbreviationXY,
                        yString,
                        unitsAbbreviationXY,
                        widthString,
                        unitsAbbreviationWH,
                        heightString,
                        unitsAbbreviationWH,
                        areaString,
                        pluralUnits.ToLower(),
                        moveTool.HostAngle.ToString("N", nfi2));
                }
                else
                {
                    newStatusText = string.Format(
                        contextStatusBarFormat,
                        xString,
                        unitsAbbreviationXY,
                        yString,
                        unitsAbbreviationXY,
                        widthString,
                        unitsAbbreviationWH,
                        heightString,
                        unitsAbbreviationWH,
                        areaString,
                        pluralUnits.ToLower());
                }

                SetStatus(newStatusText, PdnResources.GetImageResource("Icons.SelectionIcon.png"));
            }
        }

        private void Selection_Changed(object sender, EventArgs e)
        {
            UpdateRulerSelectionTinting();
            UpdateSelectionInfoInStatusBar();
        }

        private void InitializeComponent()
        {
            this.toolPulseTimer = new System.Windows.Forms.Timer();
            this.toolPulseTimer.Interval = 16;
            this.toolPulseTimer.Tick += new EventHandler(this.ToolPulseTimer_Tick);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.activeTool != null)
                {
                    this.activeTool.Dispose();
                    this.activeTool = null;
                }
            }

            base.Dispose(disposing);
        }

        public void PerformActionAsync(DocumentWorkspaceAction action)
        {
            BeginInvoke(new Procedure<DocumentWorkspaceAction>(PerformAction), new object[] { action });
        }

        public void PerformAction(DocumentWorkspaceAction action)
        {
            bool nullTool = false;

            if ((action.ActionFlags & ActionFlags.KeepToolActive) != ActionFlags.KeepToolActive)
            {
                PushNullTool();
                Update();
                nullTool = true;
            }

            try
            {
                using (new WaitCursorChanger(this))
                {
                    HistoryMemento ha = action.PerformAction(this);

                    if (ha != null)
                    {
                        History.PushNewMemento(ha);
                    }
                }
            }

            finally
            {
                if (nullTool)
                {
                    PopNullTool();
                }
            }
        }

        /// <summary>
        /// Executes a HistoryFunction in the context of this DocumentWorkspace.
        /// </summary>
        /// <param name="function">The HistoryFunction to execute.</param>
        /// <remarks>
        /// Depending on the HistoryFunction, the currently active tool may be refreshed.
        /// </remarks>
        public HistoryFunctionResult ExecuteFunction(HistoryFunction function)
        {
            HistoryFunctionResult result;

            bool nullTool = false;

            if ((function.ActionFlags & ActionFlags.KeepToolActive) != ActionFlags.KeepToolActive)
            {
                PushNullTool();
                Update();
                nullTool = true;
            }

            try
            {
                using (new WaitCursorChanger(this))
                {
                    HistoryMemento hm = null;
                    string errorText;

                    try
                    {
                        bool cancelled = false;

                        if ((function.ActionFlags & ActionFlags.ReportsProgress) != ActionFlags.ReportsProgress)
                        {
                            hm = function.Execute(this);
                        }
                        else
                        {
                            ProgressDialog pd = new ProgressDialog();
                            bool pdLoaded = false;
                            bool closeAtLoad = false;

                            EventHandler loadCallback =
                                delegate(object sender, EventArgs e)
                                {
                                    pdLoaded = true;

                                    if (closeAtLoad)
                                    {
                                        pd.Close();
                                    }
                                };

                            ProgressEventHandler progressCallback =
                                delegate(object sender, ProgressEventArgs e)
                                {
                                    if (pdLoaded)
                                    {
                                        double newValue = Utility.Clamp(e.Percent, 0.0, 100.0);
                                        pd.Value = newValue;
                                    }
                                };

                            EventHandler<EventArgs<HistoryMemento>> finishedCallback =
                                delegate(object sender, EventArgs<HistoryMemento> e)
                                {
                                    hm = e.Data;

                                    if (pdLoaded)
                                    {
                                        // TODO: fix ProgressDialog's very weird interface
                                        pd.ExternalFinish();
                                        pd.Close();
                                    }
                                    else
                                    {
                                        closeAtLoad = true;
                                    }
                                };

                            EventHandler cancelClickCallback =
                                delegate(object sender, EventArgs e)
                                {
                                    cancelled = true;
                                    function.RequestCancel();
                                    //pd.Cancellable = false;
                                };

                            pd.Text = PdnInfo.GetBareProductName();
                            pd.Description = PdnResources.GetString("ExecuteFunction.ProgressDialog.Description.Text");
                            pd.Load += loadCallback;
                            pd.Cancellable = false; //function.Cancellable;
                            pd.CancelClick += cancelClickCallback;
                            function.Progress += progressCallback;
                            function.BeginExecute(this, this, finishedCallback);
                            pd.ShowDialog(this);
                            pd.Dispose();
                        }

                        if (hm == null && !cancelled)
                        {
                            result = HistoryFunctionResult.SuccessNoOp;
                        }
                        else if (hm == null && cancelled)
                        {
                            result = HistoryFunctionResult.Cancelled;
                        }
                        else
                        {
                            result = HistoryFunctionResult.Success;
                        }

                        errorText = null;
                    }

                    catch (HistoryFunctionNonFatalException hfnfex)
                    {
                        if (hfnfex.InnerException is OutOfMemoryException)
                        {
                            result = HistoryFunctionResult.OutOfMemory;
                        }
                        else
                        {
                            result = HistoryFunctionResult.NonFatalError;
                        }

                        if (hfnfex.LocalizedErrorText != null)
                        {
                            errorText = hfnfex.LocalizedErrorText;
                        }
                        else
                        {
                            if (hfnfex.InnerException is OutOfMemoryException)
                            {
                                errorText = PdnResources.GetString("ExecuteFunction.GenericOutOfMemory");
                            }
                            else
                            {
                                errorText = PdnResources.GetString("ExecuteFunction.GenericError");
                            }
                        }
                    }

                    if (errorText != null)
                    {
                        Utility.ErrorBox(this, errorText);
                    }

                    if (hm != null)
                    {
                        History.PushNewMemento(hm);
                    }
                }
            }

            finally
            {
                if (nullTool)
                {
                    PopNullTool();
                }
            }

            return result;
        }

        public override void ZoomIn()
        {
            this.ZoomBasis = ZoomBasis.ScaleFactor;
            base.ZoomIn();
        }

        public override void ZoomIn(double factor)
        {
            this.ZoomBasis = ZoomBasis.ScaleFactor;
            base.ZoomIn(factor);
        }

        public override void ZoomOut()
        {
            this.ZoomBasis = ZoomBasis.ScaleFactor;
            base.ZoomOut();
        }

        public override void ZoomOut(double factor)
        {
            this.ZoomBasis = ZoomBasis.ScaleFactor;
            base.ZoomOut(factor);
        }

        // TODO:
        /// <summary>
        /// Same as PerformAction(Type) except it lets you rename the HistoryMemento's name.
        /// </summary>
        /// <param name="actionType"></param>
        /// <param name="newName"></param>
        public void PerformAction(Type actionType, string newName, ImageResource icon)
        {
            using (new WaitCursorChanger(this))
            {
                ConstructorInfo ci = actionType.GetConstructor(new Type[] { typeof(DocumentWorkspace) });
                object actionAsObject = ci.Invoke(new object[] { this });
                DocumentWorkspaceAction action = actionAsObject as DocumentWorkspaceAction;

                if (action != null)
                {
                    bool nullTool = false;

                    if ((action.ActionFlags & ActionFlags.KeepToolActive) != ActionFlags.KeepToolActive)
                    {
                        PushNullTool();
                        Update();
                        nullTool = true;
                    }

                    try
                    {
                        HistoryMemento ha = action.PerformAction(this);

                        if (ha != null)
                        {
                            ha.Name = newName;
                            ha.Image = icon;
                            History.PushNewMemento(ha);
                        }
                    }

                    finally
                    {
                        if (nullTool)
                        {
                            PopNullTool();
                        }
                    }
                }
            }
        }
        
        public event EventHandler ZoomBasisChanging;
        protected virtual void OnZoomBasisChanging()
        {
            if (ZoomBasisChanging != null)
            {
                ZoomBasisChanging(this, EventArgs.Empty);
            }
        }

        public event EventHandler ZoomBasisChanged;
        protected virtual void OnZoomBasisChanged()
        {
            if (ZoomBasisChanged != null)
            {
                ZoomBasisChanged(this, EventArgs.Empty);
            }
        }

        public ZoomBasis ZoomBasis
        {
            get
            {
                return this.zoomBasis;
            }

            set
            {
                if (this.zoomBasis != value)
                {
                    OnZoomBasisChanging();
                    this.zoomBasis = value;

                    switch (this.zoomBasis)
                    {
                        case ZoomBasis.FitToWindow:
                            ZoomToWindow();

                            // Enable PanelAutoScroll only long enough to recenter the view
                            PanelAutoScroll = true;
                            PanelAutoScroll = false;

                            // this would be unset by the scalefactor changes in ZoomToWindow
                            this.zoomBasis = ZoomBasis.FitToWindow;
                            break;

                        case ZoomBasis.ScaleFactor:
                            PanelAutoScroll = true;
                            break;

                        default:
                            throw new InvalidEnumArgumentException();
                    }

                    OnZoomBasisChanged();
                }
            }
        }

        public void ZoomToSelection()
        {
            if (Selection.IsEmpty)
            {
                ZoomToWindow();
            }
            else
            {
                using (PdnRegion region = Selection.CreateRegion())
                {
                    ZoomToRectangle(region.GetBoundsInt());
                }
            }
        }

        public void ZoomToRectangle(Rectangle selectionBounds)
        {
            PointF selectionCenter = new PointF((selectionBounds.Left + selectionBounds.Right + 1) / 2,
                (selectionBounds.Top + selectionBounds.Bottom + 1) / 2);

            PointF cornerPosition;

            ScaleFactor zoom = ScaleFactor.Min(ClientRectangleMin.Width, selectionBounds.Width + 2,
                                               ClientRectangleMin.Height, selectionBounds.Height + 2,
                                               ScaleFactor.MinValue);

            // Zoom out to fit the image
            ZoomBasis = ZoomBasis.ScaleFactor;
            ScaleFactor = zoom;

            cornerPosition = new PointF(selectionCenter.X - (VisibleDocumentRectangleF.Width / 2),
                selectionCenter.Y - (VisibleDocumentRectangleF.Height / 2));

            DocumentScrollPositionF = cornerPosition;
        }

        protected override void HandleMouseWheel(Control sender, MouseEventArgs e)
        {
            if (Control.ModifierKeys == Keys.Control)
            {
                double mouseDelta = (double)e.Delta / 120.0f;
                Rectangle visibleDocBoundsStart = this.VisibleDocumentBounds;
                Point mouseDocPt = this.MouseToDocument(sender, new Point(e.X, e.Y));
                RectangleF visibleDocDocRect1 = this.VisibleDocumentRectangleF;

                PointF mouseNPt = new PointF(
                    (mouseDocPt.X - visibleDocDocRect1.X) / visibleDocDocRect1.Width,
                    (mouseDocPt.Y - visibleDocDocRect1.Y) / visibleDocDocRect1.Height);

                const double factor = 1.12;
                double mouseFactor = Math.Pow(factor, Math.Abs(mouseDelta));

                if (e.Delta > 0)
                {
                    this.ZoomIn(mouseFactor);
                }
                else if (e.Delta < 0)
                {
                    this.ZoomOut(mouseFactor);
                }

                RectangleF visibleDocDocRect2 = this.VisibleDocumentRectangleF;

                PointF scrollPt2 = new PointF(
                    mouseDocPt.X - visibleDocDocRect2.Width * mouseNPt.X,
                    mouseDocPt.Y - visibleDocDocRect2.Height * mouseNPt.Y);

                this.DocumentScrollPositionF = scrollPt2;

                Rectangle visibleDocBoundsEnd = this.VisibleDocumentBounds;

                if (visibleDocBoundsEnd != visibleDocBoundsStart)
                {
                    // Make sure the screen updates, otherwise it can get a little funky looking
                    this.Update();
                }
            }

            base.HandleMouseWheel(sender, e);
        }

        public void SelectClosestVisibleLayer(Layer layer)
        {
            int oldLayerIndex = this.Document.Layers.IndexOf(layer);
            int newLayerIndex = oldLayerIndex;

            // find the closest layer that is still visible
            for (int i = 0; i < this.Document.Layers.Count; ++i)
            {
                int lower = oldLayerIndex - i;
                int upper = oldLayerIndex + i;

                if (lower >= 0 && lower < this.Document.Layers.Count && ((Layer)this.Document.Layers[lower]).Visible)
                {
                    newLayerIndex = lower;
                    break;
                }

                if (upper >= 0 && upper < this.Document.Layers.Count && ((Layer)this.Document.Layers[upper]).Visible)
                {
                    newLayerIndex = upper;
                    break;
                }
            }

            if (newLayerIndex != oldLayerIndex)
            {
                this.ActiveLayer = (Layer)Document.Layers[newLayerIndex];
            }
        }

        public void UpdateRulerSelectionTinting()
        {
            if (this.RulersEnabled)
            {
                Rectangle bounds = this.Selection.GetBounds();
                this.SetHighlightRectangle(bounds);
            }
        }

        private void LayerRemovingHandler(object sender, IndexEventArgs e)
        {
            Layer layer = (Layer)this.Document.Layers[e.Index];
            layer.PropertyChanging -= LayerPropertyChangingHandler;
            layer.PropertyChanged -= LayerPropertyChangedHandler;

            // pick a new valid layer!
            int newLayerIndex;

            if (e.Index == this.Document.Layers.Count - 1)
            {
                newLayerIndex = e.Index - 1;
            }
            else
            {
                newLayerIndex = e.Index + 1;
            }

            if (newLayerIndex >= 0 && newLayerIndex < this.Document.Layers.Count)
            {
                this.ActiveLayer = (Layer)this.Document.Layers[newLayerIndex];
            }
            else
            {
                if (this.Document.Layers.Count == 0)
                {
                    this.ActiveLayer = null;
                }
                else
                {
                    this.ActiveLayer = (Layer)this.Document.Layers[0];
                }
            }
        }

        private void LayerRemovedHandler(object sender, IndexEventArgs e)
        {
        }

        private void LayerInsertedHandler(object sender, IndexEventArgs e)
        {
            Layer layer = (Layer)this.Document.Layers[e.Index];
            this.ActiveLayer = layer;
            layer.PropertyChanging += LayerPropertyChangingHandler;
            layer.PropertyChanged += LayerPropertyChangedHandler;
        }

        private void LayerPropertyChangingHandler(object sender, PropertyEventArgs e)
        {
            string nameFormat = PdnResources.GetString("LayerPropertyChanging.HistoryMementoNameFormat");
            string haName = string.Format(nameFormat, e.PropertyName);

            LayerPropertyHistoryMemento lpha = new LayerPropertyHistoryMemento(
                haName,
                PdnResources.GetImageResource("Icons.MenuLayersLayerPropertiesIcon.png"),
                this,
                this.Document.Layers.IndexOf(sender));

            this.History.PushNewMemento(lpha);
        }

        private void LayerPropertyChangedHandler(object sender, PropertyEventArgs e)
        {
            Layer layer = (Layer)sender;

            if (!layer.Visible && 
                layer == this.ActiveLayer && 
                this.Document.Layers.Count > 1 &&
                !History.IsExecutingMemento)
            {
                SelectClosestVisibleLayer(layer);
            }
        }

        private void ToolPulseTimer_Tick(object sender, EventArgs e)
        {
            if (FindForm() == null || FindForm().WindowState == FormWindowState.Minimized)
            {
                return;
            }

            if (this.Tool != null && this.Tool.Active)
            {
                this.Tool.PerformPulse();
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            if (this.appWorkspace == null)
            {
                throw new InvalidOperationException("Must set the Workspace property");
            }

            base.OnLoad(e);
        }

        public event EventHandler ActiveLayerChanging;
        protected void OnLayerChanging()
        {
            if (ActiveLayerChanging != null)
            {
                ActiveLayerChanging(this, EventArgs.Empty);
            }
        }

        public event EventHandler ActiveLayerChanged;
        protected void OnLayerChanged()
        {
            this.Focus();

            if (ActiveLayerChanged != null)
            {
                ActiveLayerChanged(this, EventArgs.Empty);
            }
        }

        public Layer ActiveLayer
        {
            get
            {
                return this.activeLayer;
            }

            set
            {
                OnLayerChanging();

                bool deactivateTool;

                if (this.Tool != null)
                {
                    deactivateTool = this.Tool.DeactivateOnLayerChange;
                }
                else
                {
                    deactivateTool = false;
                }

                if (deactivateTool)
                {
                    PushNullTool();
                    this.EnableToolPulse = false;
                }

                try
                {
                    // Verify that the layer is in the document (sanity checking)
                    if (this.Document != null)
                    {
                        if (value != null && !this.Document.Layers.Contains(value))
                        {
                            throw new InvalidOperationException("ActiveLayer was changed to a layer that is not contained within the Document");
                        }
                    }
                    else
                    {   
                        // Document == null
                        if (value != null)
                        {
                            throw new InvalidOperationException("ActiveLayer was set to non-null while Document was null");
                        }
                    }

                    // Finally, set the field.
                    this.activeLayer = value;
                }

                finally
                {
                    if (deactivateTool)
                    {
                        PopNullTool();
                        this.EnableToolPulse = true;
                    }
                }

                OnLayerChanged();
            }
        }

        public int ActiveLayerIndex
        {
            get
            {
                return Document.Layers.IndexOf(ActiveLayer);
            }

            set
            {
                this.ActiveLayer = (Layer)Document.Layers[value];
            }
        }

        public bool EnableToolPulse
        {
            get
            {
                return this.toolPulseTimer.Enabled;
            }

            set
            {
                this.toolPulseTimer.Enabled = value;
            }
        }

        public HistoryStack History
        {
            get
            {
                return this.history;
            }
        }

        public Tool Tool
        {
            get
            {
                return this.activeTool;
            }
        }

        public Type GetToolType()
        {
            if (Tool != null)
            {
                return Tool.GetType();
            }
            else
            {
                return null;
            }
        }

        public void SetToolFromType(Type toolType)
        {
            if (toolType == GetToolType())
            {
                return;
            }
            else if (toolType == null)
            {
                SetTool(null);
            }
            else
            {
                Tool newTool = CreateTool(toolType);
                SetTool(newTool);
            }
        }

        public void PushNullTool()
        {
            if (this.nullToolCount == 0)
            {
                this.preNullTool = GetToolType();
                this.SetTool(null);
                this.nullToolCount = 1;
            }
            else
            {
                ++this.nullToolCount;
            }
        }

        public void PopNullTool()
        {
            --this.nullToolCount;

            if (this.nullToolCount == 0)
            {
                this.SetToolFromType(this.preNullTool);
                this.preNullTool = null;
            }
            else if (this.nullToolCount < 0)
            {
                throw new InvalidOperationException("PopNullTool() call was not matched with PushNullTool()");
            }
        }

        public Type PreviousActiveToolType
        {
            get
            {
                return this.previousActiveToolType;
            }
        }

        public void SetTool(Tool copyMe)
        {
            OnToolChanging();

            if (this.activeTool != null)
            {
                this.previousActiveToolType = this.activeTool.GetType();
                this.activeTool.CursorChanged -= ToolCursorChangedHandler;
                this.activeTool.PerformDeactivate();
                this.activeTool.Dispose();
                this.activeTool = null;
            }

            if (copyMe == null)
            {
                EnableToolPulse = false;
            }
            else
            {
                Tracing.LogFeature("SetTool(" + copyMe.GetType().FullName + ")");
                this.activeTool = CreateTool(copyMe.GetType());
                this.activeTool.PerformActivate();
                this.activeTool.CursorChanged += ToolCursorChangedHandler;

                if (this.suspendToolCursorChanges <= 0)
                {
                    Cursor = this.activeTool.Cursor;
                }

                EnableToolPulse = true;
            }

            OnToolChanged();
        }

        public Tool CreateTool(Type toolType)
        {
            return DocumentWorkspace.CreateTool(toolType, this);
        }

        private static Tool CreateTool(Type toolType, DocumentWorkspace dc)
        {
            ConstructorInfo ci = toolType.GetConstructor(new Type[] { typeof(DocumentWorkspace) });
            Tool tool = (Tool)ci.Invoke(new object[] { dc });
            return tool;
        }

        private static void InitializeTools()
        {
            // add all the tools
            tools = new Type[] 
            {
                typeof(RectangleSelectTool),
                typeof(MoveTool),
                typeof(LassoSelectTool),
                typeof(MoveSelectionTool),

                typeof(EllipseSelectTool),
                typeof(ZoomTool),

                typeof(MagicWandTool),
                typeof(PanTool),

                typeof(PaintBucketTool),
                typeof(GradientTool),

                typeof(PaintBrushTool),
                typeof(EraserTool),
                typeof(PencilTool),
                typeof(ColorPickerTool),
                typeof(CloneStampTool), 
                typeof(RecolorTool),
                typeof(TextTool),

                typeof(LineTool),
                typeof(RectangleTool),
                typeof(RoundedRectangleTool),
                typeof(EllipseTool),
                typeof(FreeformShapeTool),
            };
        }

        private static void InitializeToolInfos()
        {
            int i = 0;
            toolInfos = new ToolInfo[tools.Length];

            foreach (Type toolType in tools)
            {
                using (Tool tool = DocumentWorkspace.CreateTool(toolType, null))
                {
                    toolInfos[i] = tool.Info;
                    ++i;
                }
            }
        }

        public static Type[] Tools
        {
            get
            {
                return (Type[])tools.Clone();
            }
        }

        public static ToolInfo[] ToolInfos
        {
            get
            {
                return (ToolInfo[])toolInfos.Clone();
            }
        }

        public event EventHandler ToolChanging;
        protected void OnToolChanging()
        {
            if (ToolChanging != null)
            {
                ToolChanging(this, EventArgs.Empty);
            }
        }

        public event EventHandler ToolChanged;
        protected void OnToolChanged()
        {
            if (ToolChanged != null)
            {
                ToolChanged(this, EventArgs.Empty);
            }
        }

        private void ToolCursorChangedHandler(object sender, EventArgs e)
        {
            if (this.suspendToolCursorChanges <= 0)
            {
                Cursor = this.activeTool.Cursor;
            }
        }

        // Note: static tool data is removed whenever the Document changes
        // TODO: shouldn't this be moved to the Tool class somehow?
        public object GetStaticToolData(Type toolType)
        {
            return staticToolData[toolType];
        }

        public void SetStaticToolData(Type toolType, object data)
        {
            staticToolData[toolType] = data;
        }

        public AppWorkspace AppWorkspace
        {
            get
            {
                return this.appWorkspace;
            }

            set
            {
                this.appWorkspace = value;
            }
        }

        public Selection Selection
        {
            get
            {
                return this.selection;
            }
        }

        public bool EnableOutlineAnimation
        {
            get
            {
                return this.selectionRenderer.EnableOutlineAnimation;
            }

            set
            {
                this.selectionRenderer.EnableOutlineAnimation = value;
            }
        }

        public bool EnableSelectionOutline
        {
            get
            {
                return this.selectionRenderer.EnableSelectionOutline;
            }

            set
            {
                this.selectionRenderer.EnableSelectionOutline = value;
            }
        }

        public bool EnableSelectionTinting
        {
            get
            {
                return this.selectionRenderer.EnableSelectionTinting;
            }

            set
            {
                this.selectionRenderer.EnableSelectionTinting = value;
            }
        }

        public void ResetOutlineWhiteOpacity()
        {
            this.selectionRenderer.ResetOutlineWhiteOpacity();
        }

        public event EventHandler FilePathChanged;
        protected virtual void OnFilePathChanged()
        {
            if (FilePathChanged != null)
            {
                FilePathChanged(this, EventArgs.Empty);
            }
        }

        public string FilePath
        {
            get
            {
                return this.filePath;
            }
        }

        public string GetFriendlyName()
        {
            string friendlyName;

            if (this.filePath != null)
            {
                friendlyName = Path.GetFileName(this.filePath);
            }
            else
            {
                friendlyName = PdnResources.GetString("Untitled.FriendlyName");
            }

            return friendlyName;
        }

        public FileType FileType
        {
            get
            {
                return this.fileType;
            }
        }

        public SaveConfigToken SaveConfigToken
        {
            get
            {
                if (this.saveConfigToken == null)
                {
                    return null;
                }
                else
                {
                    return (SaveConfigToken)this.saveConfigToken.Clone();
                }
            }
        }

        public event EventHandler SaveOptionsChanged;
        protected virtual void OnSaveOptionsChanged()
        {
            if (SaveOptionsChanged != null)
            {
                SaveOptionsChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Sets the FileType and SaveConfigToken parameters that are used if the
        /// user chooses "Save" from the File menu. These are not used by the
        /// DocumentControl class and should be used by whoever actually goes
        /// to save the Document instance.
        /// </summary>
        /// <param name="fileType"></param>
        /// <param name="saveParameters"></param>
        public void SetDocumentSaveOptions(string newFilePath, FileType newFileType, SaveConfigToken newSaveConfigToken)
        {
            this.filePath = newFilePath;
            OnFilePathChanged();

            this.fileType = newFileType;

            if (newSaveConfigToken == null)
            {
                this.saveConfigToken = null;
            }
            else
            {
                this.saveConfigToken = (SaveConfigToken)newSaveConfigToken.Clone();
            }

            OnSaveOptionsChanged();
        }

        public void GetDocumentSaveOptions(out string filePathResult, out FileType fileTypeResult, out SaveConfigToken saveConfigTokenResult)
        {
            filePathResult = this.filePath;
            fileTypeResult = this.fileType;

            if (this.saveConfigToken == null)
            {
                saveConfigTokenResult = null;
            }
            else
            {
                saveConfigTokenResult = (SaveConfigToken)this.saveConfigToken.Clone();
            }
        }

        private bool isScratchSurfaceBorrowed = false;
        private string borrowScratchSurfaceReason = string.Empty;

        /// The scratch, stencil, accumulation, whatever buffer. This is used by many parts
        /// of Paint.NET as a temporary area for which to store data.
        /// This surface is 'owned' by any Tool that is active. If you want to use this you
        /// must first deactivate the Tool using PushNullTool() and then reactivate it when 
        /// you are finished by calling PopNullTool().
        /// Tools should use Tool.ScratchSurface instead of these API's.

        public Surface BorrowScratchSurface(string reason)
        {
            if (this.isScratchSurfaceBorrowed)
            {
                throw new InvalidOperationException(
                    "ScratchSurface already borrowed: '" + 
                    this.borrowScratchSurfaceReason + 
                    "' (trying to borrow for: '" + reason + "')");
            }

            Tracing.Ping("Borrowing scratchSurface: " + reason);
            this.isScratchSurfaceBorrowed = true;
            this.borrowScratchSurfaceReason = reason;
            return this.scratchSurface;
        }

        public void ReturnScratchSurface(Surface borrowedScratchSurface)
        {
            if (!this.isScratchSurfaceBorrowed)
            {
                throw new InvalidOperationException("ScratchSurface wasn't borrowed");
            }

            if (this.scratchSurface != borrowedScratchSurface)
            {
                throw new InvalidOperationException("returned ScratchSurface doesn't match the real one");
            }

            Tracing.Ping("Returning scratchSurface: " + this.borrowScratchSurfaceReason);
            this.isScratchSurfaceBorrowed = false;
            this.borrowScratchSurfaceReason = string.Empty;
        }

        /// <summary>
        /// Updates any pertinent EXIF tags, such as "Creation Software", to be
        /// relevant or up-to-date.
        /// </summary>
        /// <param name="document"></param>
        private void UpdateExifTags(Document document)
        {
            // We want it to say "Creation Software: Paint.NET vX.Y"
            // I have verified that other image editing software overwrites this tag,
            // and does not just add it when it does not exist.
            PropertyItem pi = Exif.CreateAscii(ExifTagID.Software, PdnInfo.GetProductName(false));
            document.Metadata.ReplaceExifValues(ExifTagID.Software, new PropertyItem[1] { pi });
        }
        
        private ZoomBasis savedZb;
        private ScaleFactor savedSf;
        private int savedAli;
        protected override void OnDocumentChanging(Document newDocument)
        {
            base.OnDocumentChanging(newDocument);

            this.savedZb = this.ZoomBasis;
            this.savedSf = ScaleFactor;

            if (this.ActiveLayer != null)
            {
                this.savedAli = ActiveLayerIndex;
            }
            else
            {
                this.savedAli = -1;
            }

            if (newDocument != null)
            {
                UpdateExifTags(newDocument);
            }

            if (this.Document != null)
            {
                foreach (Layer layer in this.Document.Layers)
                {
                    layer.PropertyChanging -= LayerPropertyChangingHandler;
                    layer.PropertyChanged -= LayerPropertyChangedHandler;
                }

                this.Document.Layers.RemovingAt -= LayerRemovingHandler;
                this.Document.Layers.RemovedAt -= LayerRemovedHandler;
                this.Document.Layers.Inserted -= LayerInsertedHandler;
            }
            
            this.staticToolData.Clear();

            PushNullTool(); // matching Pop is in OnDocumetChanged()
            ActiveLayer = null;

            if (this.scratchSurface != null)
            {
                if (this.isScratchSurfaceBorrowed)
                {
                    throw new InvalidOperationException("scratchSurface is currently borrowed: " + this.borrowScratchSurfaceReason);
                }

                if (newDocument == null || newDocument.Size != this.scratchSurface.Size)
                {
                    this.scratchSurface.Dispose();
                    this.scratchSurface = null;
                }
            }

            if (!Selection.IsEmpty)
            {
                Selection.Reset();
            }
        }

        protected override void OnDocumentChanged()
        {
            // if the ActiveLayer is not in this new document, then
            // we try to set ActiveLayer to the first layer in this
            // new document. But if the document contains no layers,
            // or is null, we just null the ActiveLayer.
            if (this.Document == null)
            {
                this.ActiveLayer = null;
            }
            else
            {
                if (this.activeTool != null)
                {
                    throw new InvalidOperationException("Tool was not deactivated while Document was being changed");
                }

                if (this.scratchSurface != null)
                {
                    if (this.isScratchSurfaceBorrowed)
                    {
                        throw new InvalidOperationException("scratchSurface is currently borrowed: " + this.borrowScratchSurfaceReason);
                    }

                    if (Document == null || this.scratchSurface.Size != Document.Size)
                    {
                        this.scratchSurface.Dispose();
                        this.scratchSurface = null;
                    }
                }

                this.scratchSurface = new Surface(this.Document.Size);

                this.Selection.ClipRectangle = this.Document.Bounds;

                foreach (Layer layer in this.Document.Layers)
                {
                    layer.PropertyChanging += LayerPropertyChangingHandler;
                    layer.PropertyChanged += LayerPropertyChangedHandler;
                }

                this.Document.Layers.RemovingAt += LayerRemovingHandler;
                this.Document.Layers.RemovedAt += LayerRemovedHandler;
                this.Document.Layers.Inserted += LayerInsertedHandler;

                if (!this.Document.Layers.Contains(this.ActiveLayer))
                {
                    if (this.Document.Layers.Count > 0)
                    {
                        if (savedAli >= 0 && savedAli < this.Document.Layers.Count)
                        {
                            this.ActiveLayer = (Layer)this.Document.Layers[savedAli];
                        }
                        else
                        {
                            this.ActiveLayer = (Layer)this.Document.Layers[0];
                        }
                    }
                    else
                    {
                        this.ActiveLayer = null;
                    }
                }

                // we invalidate each layer so that the layer previews refresh themselves
                foreach (Layer layer in this.Document.Layers)
                {
                    layer.Invalidate();
                }

                bool oldDirty = this.Document.Dirty;
                this.Document.Invalidate();
                this.Document.Dirty = oldDirty;

                this.ZoomBasis = this.savedZb;
                if (this.savedZb == ZoomBasis.ScaleFactor)
                {
                    ScaleFactor = this.savedSf;
                }
            }

            PopNullTool();
            AutoScrollPosition = new Point(0, 0);

            base.OnDocumentChanged();
        }

        /// <summary>
        /// Takes the current Document from this DocumentWorkspace instance and adds it to the MRU list.
        /// </summary>
        /// <param name="fileName"></param>
        public void AddToMruList()
        {
            using (new PushNullToolMode(this))
            {
                string fullFileName = Path.GetFullPath(this.FilePath);
                int edgeLength = AppWorkspace.MostRecentFiles.IconSize;
                Surface thumb1 = RenderThumbnail(edgeLength, true, true);

                // Put it inside a square bitmap
                Surface thumb = new Surface(4 + edgeLength, 4 + edgeLength);

                thumb.Clear(ColorBgra.Transparent);

                Rectangle dstRect = new Rectangle((thumb.Width - thumb1.Width) / 2,
                    (thumb.Height - thumb1.Height) / 2, thumb1.Width, thumb1.Height);

                thumb.CopySurface(thumb1, dstRect.Location);

                using (RenderArgs ra = new RenderArgs(thumb))
                {
                    // Draw black border
                    Rectangle borderRect = new Rectangle(dstRect.Left - 1, dstRect.Top - 1, dstRect.Width + 2, dstRect.Height + 2);
                    --borderRect.Width;
                    --borderRect.Height;
                    ra.Graphics.DrawRectangle(Pens.Black, borderRect);

                    Rectangle shadowRect = Rectangle.Inflate(borderRect, 1, 1);
                    ++shadowRect.Width;
                    ++shadowRect.Height;
                    Utility.DrawDropShadow1px(ra.Graphics, shadowRect);

                    thumb1.Dispose();
                    thumb1 = null;

                    MostRecentFile mrf = new MostRecentFile(fullFileName, Utility.FullCloneBitmap(ra.Bitmap));

                    if (AppWorkspace.MostRecentFiles.Contains(fullFileName))
                    {
                        AppWorkspace.MostRecentFiles.Remove(fullFileName);
                    }

                    AppWorkspace.MostRecentFiles.Add(mrf);
                    AppWorkspace.MostRecentFiles.SaveMruList();
                }
            }
        }

        /// <summary>
        /// Shows an OpenFileDialog or SaveFileDialog and populates the InitialDirectory from the global
        /// settings repository if possible.
        /// </summary>
        /// <param name="fd">The FileDialog to show.</param>
        /// <remarks>
        /// The FileDialog should already have its InitialDirectory populated as a suggestion of where to start.
        /// </remarks>
        public static DialogResult ShowFileDialog(Control owner, IFileDialog fd)
        {
            string initialDirectory = Settings.CurrentUser.GetString(SettingNames.LastFileDialogDirectory, fd.InitialDirectory);

            // TODO: spawn this in a background thread, if it doesn't respond within ~500ms?, assume the dir doesn't exist
            bool dirExists = false;

            try 
            {
                DirectoryInfo dirInfo = new DirectoryInfo(initialDirectory);

                using (new WaitCursorChanger(owner))
                {
                    dirExists = dirInfo.Exists;

                    if (!dirInfo.Exists)
                    {
                        initialDirectory = fd.InitialDirectory;
                    }
                }
            }

            catch (Exception)
            {
                initialDirectory = fd.InitialDirectory;
            }

            fd.InitialDirectory = initialDirectory;

            OurFileDialogUICallbacks ouc = new OurFileDialogUICallbacks();
            DialogResult result = fd.ShowDialog(owner, ouc);

            if (result == DialogResult.OK)
            {
                string fileName;
                
                if (fd is IFileOpenDialog)
                {
                    string[] fileNames = ((IFileOpenDialog)fd).FileNames;

                    if (fileNames.Length > 0)
                    {
                        fileName = fileNames[0];
                    }
                    else
                    {
                        fileName = null;
                    }
                }
                else if (fd is IFileSaveDialog)
                {
                    fileName = ((IFileSaveDialog)fd).FileName;
                }
                else
                {
                    throw new InvalidOperationException();
                }

                if (fileName != null)
                {
                    string newDir = Path.GetDirectoryName(fileName);
                    Settings.CurrentUser.SetString(SettingNames.LastFileDialogDirectory, newDir);
                }
                else
                {
                    throw new FileNotFoundException();
                }
            }

            return result;
        }

        private sealed class OurFileDialogUICallbacks
            : IFileDialogUICallbacks
        {
            public FileOverwriteAction ShowOverwritePrompt(IWin32Window owner, string pathName)
            {
                FileOverwriteAction returnVal;

                string title = PdnResources.GetString("SaveAs.OverwriteConfirmation.Title");
                string textFormat = PdnResources.GetString("SaveAs.OverwriteConfirmation.Text.Format");
                string fileName;

                try
                {
                    fileName = Path.GetFileName(pathName);
                }

                catch (Exception)
                {
                    fileName = pathName;
                }

                string text = string.Format(textFormat, fileName);

                DialogResult result = MessageBox.Show(owner, text, title, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);

                switch (result)
                {
                    case DialogResult.Yes:
                        returnVal = FileOverwriteAction.Overwrite;
                        break;

                    case DialogResult.No:
                        returnVal = FileOverwriteAction.Cancel;
                        break;

                    default:
                        throw new InvalidEnumArgumentException();
                }

                return returnVal;
            }

            public bool ShowError(IWin32Window owner, string filePath, Exception ex)
            {
                if (ex is PathTooLongException)
                {
                    string title = PdnInfo.GetBareProductName();
                    string message = PdnResources.GetString("FileDialog.PathTooLongException.Message");

                    MessageBox.Show(owner, message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);

                    return true;
                }
                else
                {
                    return false;
                }
            }

            public IFileTransferProgressEvents CreateFileTransferProgressEvents()
            {
                return new OurProgressEvents();
            }
        }

        private sealed class OurProgressEvents
            : IFileTransferProgressEvents
        {
            private TransferProgressDialog progressDialog;
            private ICancelable cancelSink;
            private int itemCount = 0;
            private int itemOrdinal = 0;
            private string itemName = string.Empty;
            private long totalWork;
            private long totalProgress;
            private const int maxPBValue = 200; // granularity of progress bar. 100 means 1%, 200 means 0.5%, etc.
            private bool cancelRequested = false;

            private ManualResetEvent operationEnded = new ManualResetEvent(false);

            public OurProgressEvents()
            {
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "cancelSink")]
            public void BeginOperation(IWin32Window owner, EventHandler callWhenUIShown, ICancelable cancelSink)
            {
                if (this.progressDialog != null)
                {
                    throw new InvalidOperationException("Operation already in progress");
                }

                this.progressDialog = new TransferProgressDialog();
                this.progressDialog.Text = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.TransferProgress.Title");
                this.progressDialog.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileOpenIcon.png").Reference);
                this.progressDialog.ItemText = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemText.Initializing");
                this.progressDialog.ProgressBar.Style = ProgressBarStyle.Marquee;
                this.progressDialog.ProgressBar.Maximum = maxPBValue;

                this.progressDialog.CancelClicked +=
                    delegate(object sender, EventArgs e)
                    {
                        this.cancelRequested = true;
                        this.cancelSink.RequestCancel();
                        UpdateUI();
                    };

                EventHandler progressDialog_Shown =
                    delegate(object sender, EventArgs e)
                    {
                        callWhenUIShown(this, EventArgs.Empty);
                    };

                this.cancelSink = cancelSink;
                this.itemOrdinal = 0;
                this.cancelRequested = false;
                this.itemName = string.Empty;
                this.itemCount = 0;
                this.itemOrdinal = 0;
                this.totalProgress = 0;
                this.totalWork = 0;

                this.progressDialog.Shown += progressDialog_Shown;
                this.progressDialog.ShowDialog(owner);
                this.progressDialog.Shown -= progressDialog_Shown;

                this.progressDialog.Dispose();
                this.progressDialog = null;
                this.cancelSink = null;
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "itemCount")]
            public void SetItemCount(int itemCount)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<int>(SetItemCount), new object[] { itemCount });
                }
                else
                {
                    this.itemCount = itemCount;
                    UpdateUI();
                }
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "itemOrdinal")]
            public void SetItemOrdinal(int itemOrdinal)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<int>(SetItemOrdinal), new object[] { itemOrdinal });
                }
                else
                {
                    this.itemOrdinal = itemOrdinal;
                    this.totalWork = 0;
                    this.totalProgress = 0;
                    UpdateUI();
                }
            }

            public void SetItemInfo(string itemInfo)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<string>(SetItemInfo), new object[] { itemInfo });
                }
                else
                {
                    this.itemName = itemInfo;
                    UpdateUI();
                }
            }

            public void BeginItem()
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure(BeginItem), null);
                }
                else
                {
                    this.progressDialog.ProgressBar.Style = ProgressBarStyle.Continuous;
                }
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "totalWork")]
            public void SetItemWorkTotal(long totalWork)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<long>(SetItemWorkTotal), new object[] { totalWork });
                }
                else
                {
                    this.totalWork = totalWork;
                    UpdateUI();
                }
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "totalProgress")]
            public void SetItemWorkProgress(long totalProgress)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<long>(SetItemWorkProgress), new object[] { totalProgress });
                }
                else
                {
                    this.totalProgress = totalProgress;
                    UpdateUI();
                }
            }

            public void EndItem(WorkItemResult result)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<WorkItemResult>(EndItem), new object[] { result });
                }
                else
                {
                }
            }

            public void EndOperation(OperationResult result)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    this.progressDialog.BeginInvoke(new Procedure<OperationResult>(EndOperation), new object[] { result });
                }
                else
                {
                    this.progressDialog.Close();
                }
            }

            public WorkItemFailureAction ReportItemFailure(Exception ex)
            {
                if (this.progressDialog.InvokeRequired)
                {
                    object result = this.progressDialog.Invoke(
                        new Function<WorkItemFailureAction, Exception>(ReportItemFailure), 
                        new object[] { ex });

                    return (WorkItemFailureAction)result;
                }
                else
                {
                    WorkItemFailureAction result;
                    result = ShowFileTransferFailedDialog(ex);
                    return result;
                }
            }

            private WorkItemFailureAction ShowFileTransferFailedDialog(Exception ex)
            {
                WorkItemFailureAction result;
                Icon formIcon = this.progressDialog.Icon;

                string formTitle = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemFailureDialog.Title");

                Image taskImage = PdnResources.GetImageResource("Icons.WarningIcon.png").Reference;

                string introTextFormat = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemFailureDialog.IntroText.Format");
                string introText = string.Format(introTextFormat, ex.Message);

                TaskButton retryTB = new TaskButton(
                    PdnResources.GetImageResource("Icons.MenuImageRotate90CWIcon.png").Reference,
                    PdnResources.GetString("DocumentWorkspace.ShowFileDialog.RetryTB.ActionText"),
                    PdnResources.GetString("DocumentWorkspace.ShowFileDialog.RetryTB.ExplanationText"));

                TaskButton skipTB = new TaskButton(
                    PdnResources.GetImageResource("Icons.HistoryFastForwardIcon.png").Reference,
                    PdnResources.GetString("DocumentWorkspace.ShowFileDialog.SkipTB.ActionText"),
                    PdnResources.GetString("DocumentWorkspace.ShowFileDialog.SkipTB.ExplanationText"));

                TaskButton cancelTB = new TaskButton(
                    PdnResources.GetImageResource("Icons.CancelIcon.png").Reference,
                    PdnResources.GetString("DocumentWorkspace.ShowFileDialog.CancelTB.ActionText"),
                    PdnResources.GetString("DocumentWorkspace.ShowFileDialog.CancelTB.ExplanationText"));

                List<TaskButton> taskButtons = new List<TaskButton>();
                taskButtons.Add(retryTB);

                // Only have the Skip button if there is more than 1 item being transferred.
                // If only 1 item is begin transferred, Skip and Cancel are essentially synonymous.
                if (this.itemCount > 1)
                {
                    taskButtons.Add(skipTB);
                }

                taskButtons.Add(cancelTB);

                int width96 = (TaskDialog.DefaultPixelWidth96Dpi * 4) / 3; // 33% wider

                TaskButton clickedTB = TaskDialog.Show(
                    this.progressDialog,
                    formIcon,
                    formTitle,
                    taskImage,
                    true,
                    introText,
                    taskButtons.ToArray(),
                    retryTB,
                    cancelTB,
                    width96);

                if (clickedTB == retryTB)
                {
                    result = WorkItemFailureAction.RetryItem;
                }
                else if (clickedTB == skipTB)
                {
                    result = WorkItemFailureAction.SkipItem;
                }
                else
                {
                    result = WorkItemFailureAction.CancelOperation;
                }

                return result;
            }

            private void UpdateUI()
            {
                int itemCount2 = Math.Max(1, this.itemCount);

                double startValue = (double)this.itemOrdinal / (double)itemCount2;
                double endValue = (double)(this.itemOrdinal + 1) / (double)itemCount2;

                long totalWork2 = Math.Max(1, this.totalWork);
                double lerp = (double)this.totalProgress / (double)totalWork2;

                double newValue = Utility.Lerp(startValue, endValue, lerp);
                int newValueInt = (int)Math.Ceiling(maxPBValue * newValue);

                if (this.cancelRequested)
                {
                    this.progressDialog.CancelEnabled = false;
                    this.progressDialog.ItemText = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemText.Canceling");
                    this.progressDialog.OperationProgress = string.Empty;
                    this.progressDialog.ProgressBar.Style = ProgressBarStyle.Marquee;
                }
                else
                {
                    this.progressDialog.CancelEnabled = true;
                    this.progressDialog.ItemText = this.itemName;
                    string progressFormat = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ProgressText.Format");
                    string progressText = string.Format(progressFormat, this.itemOrdinal + 1, this.itemCount);
                    this.progressDialog.OperationProgress = progressText;
                    this.progressDialog.ProgressBar.Style = ProgressBarStyle.Continuous;
                    this.progressDialog.ProgressBar.Value = newValueInt;
                }
            }
        }

        public static DialogResult ChooseFile(Control parent, out string fileName)
        {
            return ChooseFile(parent, out fileName, null);
        }

        public static DialogResult ChooseFile(Control parent, out string fileName, string startingDir)
        {
            string[] fileNames;
            DialogResult result = ChooseFiles(parent, out fileNames, false, startingDir);

            if (result == DialogResult.OK)
            {
                fileName = fileNames[0];
            }
            else
            {
                fileName = null;
            }

            return result;
        }

        public static DialogResult ChooseFiles(Control owner, out string[] fileNames, bool multiselect)
        {
            return ChooseFiles(owner, out fileNames, multiselect, null);
        }

        public static DialogResult ChooseFiles(Control owner, out string[] fileNames, bool multiselect, string startingDir)
        {
            FileTypeCollection fileTypes = FileTypes.GetFileTypes();

            using (IFileOpenDialog ofd = SystemLayer.CommonDialogs.CreateFileOpenDialog())
            {
                if (startingDir != null)
                {
                    ofd.InitialDirectory = startingDir;
                }
                else
                {
                    ofd.InitialDirectory = GetDefaultSavePath();
                }

                ofd.CheckFileExists = true;
                ofd.CheckPathExists = true;
                ofd.Multiselect = multiselect;

                ofd.Filter = fileTypes.ToString(true, PdnResources.GetString("FileDialog.Types.AllImages"), false, true);
                ofd.FilterIndex = 0;

                DialogResult result = ShowFileDialog(owner, ofd);

                if (result == DialogResult.OK)
                {
                    fileNames = ofd.FileNames;
                }
                else
                {
                    fileNames = new string[0];
                }

                return result;
            }
        }

        /// <summary>
        /// Use this to get a save config token. You should already know the filename and file type.
        /// An existing save config token is optional and will be used to pre-populate the config dialog.
        /// </summary>
        /// <param name="fileType"></param>
        /// <param name="saveConfigToken"></param>
        /// <param name="newSaveConfigToken"></param>
        /// <returns>false if the user cancelled, otherwise true</returns>
        private bool GetSaveConfigToken(
            FileType currentFileType, 
            SaveConfigToken currentSaveConfigToken, 
            out SaveConfigToken newSaveConfigToken, 
            Surface saveScratchSurface)
        {
            if (currentFileType.SupportsConfiguration)
            {
                using (SaveConfigDialog scd = new SaveConfigDialog())
                {
                    scd.ScratchSurface = saveScratchSurface;

                    ProgressEventHandler peh = delegate(object sender, ProgressEventArgs e)
                    {
                        if (e.Percent < 0 || e.Percent >= 100)
                        {
                            AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar();
                            AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar();
                        }
                        else
                        {
                            AppWorkspace.Widgets.StatusBarProgress.SetProgressStatusBar(e.Percent);
                        }
                    };

                    //if (currentFileType.SavesWithProgress)
                    {
                        scd.Progress += peh;
                    }

                    scd.Document = Document;
                    scd.FileType = currentFileType;

                    SaveConfigToken token = currentFileType.GetLastSaveConfigToken();
                    if (currentSaveConfigToken != null &&
                        token.GetType() == currentSaveConfigToken.GetType())
                    {
                        scd.SaveConfigToken = currentSaveConfigToken;
                    }

                    scd.EnableInstanceOpacity = false;

                    // show configuration/preview dialog
                    DialogResult dr = scd.ShowDialog(this);

                    //if (currentFileType.SavesWithProgress)
                    {
                        scd.Progress -= peh;
                        AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar();
                        AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar();
                    }

                    if (dr == DialogResult.OK)
                    {
                        newSaveConfigToken = scd.SaveConfigToken;
                        return true;
                    }
                    else
                    {
                        newSaveConfigToken = null;
                        return false;
                    }
                }
            }
            else
            {
                newSaveConfigToken = currentFileType.GetLastSaveConfigToken();
                return true;
            }
        }

        /// <summary>
        /// Used to set the file name, file type, and save config token
        /// </summary>
        /// <param name="newFileName"></param>
        /// <param name="newFileType"></param>
        /// <param name="newSaveConfigToken"></param>
        /// <returns>true if the user clicked through and accepted, or false if they cancelled at any point</returns>
        private bool DoSaveAsDialog(
            out string newFileName, 
            out FileType newFileType, 
            out SaveConfigToken newSaveConfigToken, 
            Surface saveScratchSurface)
        {
            FileTypeCollection fileTypes = FileTypes.GetFileTypes();

            using (IFileSaveDialog sfd = SystemLayer.CommonDialogs.CreateFileSaveDialog())
            {
                sfd.AddExtension = true;
                sfd.CheckPathExists = true;
                sfd.OverwritePrompt = true;
                string filter = fileTypes.ToString(false, null, true, false);
                sfd.Filter = filter;

                string localFileName;
                FileType localFileType;
                SaveConfigToken localSaveConfigToken;
                GetDocumentSaveOptions(out localFileName, out localFileType, out localSaveConfigToken);

                if (Document.Layers.Count > 1 && 
                    localFileType != null && 
                    !localFileType.SupportsLayers)
                {
                    localFileType = null;
                }

                if (localFileType == null)
                {
                    if (Document.Layers.Count == 1)
                    {
                        localFileType = PdnFileTypes.Png;
                    }
                    else
                    {
                        localFileType = PdnFileTypes.Pdn;
                    }

                    localFileName = Path.ChangeExtension(localFileName, localFileType.DefaultExtension);
                }

                if (localFileName == null)
                {
                    string name = GetDefaultSaveName();
                    string newName = Path.ChangeExtension(name, localFileType.DefaultExtension);
                    localFileName = Path.Combine(GetDefaultSavePath(), newName);
                }

                // If the filename is only an extension (i.e. ".lmnop") then we must treat it specially
                string fileNameOnly = Path.GetFileName(localFileName);
                if (fileNameOnly.Length >= 1 && fileNameOnly[0] == '.')
                {
                    sfd.FileName = localFileName;
                }
                else
                {
                    sfd.FileName = Path.ChangeExtension(localFileName, null);
                }

                sfd.FilterIndex = 1 + fileTypes.IndexOfFileType(localFileType);
                sfd.InitialDirectory = Path.GetDirectoryName(localFileName);
                sfd.Title = PdnResources.GetString("SaveAsDialog.Title");

                DialogResult dr1 = ShowFileDialog(this, sfd);
                bool result;

                if (dr1 != DialogResult.OK)
                {
                    result = false;
                }
                else
                {
                    localFileName = sfd.FileName;
                    FileType fileType2 = fileTypes[sfd.FilterIndex - 1];
                    result = GetSaveConfigToken(fileType2, localSaveConfigToken, out localSaveConfigToken, saveScratchSurface);
                    localFileType = fileType2;
                }

                if (result)
                {
                    newFileName = localFileName;
                    newFileType = localFileType;
                    newSaveConfigToken = localSaveConfigToken;
                }
                else
                {
                    newFileName = null;
                    newFileType = null;
                    newSaveConfigToken = null;
                }

                return result;
            }
        }

        /// <summary>
        /// Warns the user that we need to flatten the image.
        /// </summary>
        /// <returns>Returns DialogResult.Yes if they want to proceed or DialogResult.No if they don't.</returns>
        private DialogResult WarnAboutFlattening()
        {
            Icon formIcon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileSaveIcon.png").Reference);
            string formTitle = PdnResources.GetString("WarnAboutFlattening.Title");

            string introText = PdnResources.GetString("WarnAboutFlattening.IntroText");
            Image taskImage = null;

            TaskButton flattenTB = new TaskButton(
                PdnResources.GetImageResource("Icons.MenuImageFlattenIcon.png").Reference,
                PdnResources.GetString("WarnAboutFlattening.FlattenTB.ActionText"),
                PdnResources.GetString("WarnAboutFlattening.FlattenTB.ExplanationText"));

            TaskButton cancelTB = new TaskButton(
                TaskButton.Cancel.Image,
                PdnResources.GetString("WarnAboutFlattening.CancelTB.ActionText"),
                PdnResources.GetString("WarnAboutFlattening.CancelTB.ExplanationText"));

            TaskButton clickedTB = TaskDialog.Show(
                AppWorkspace,
                formIcon,
                formTitle,
                taskImage,
                true,
                introText,
                new TaskButton[] { flattenTB, cancelTB },
                flattenTB,
                cancelTB,
                (TaskDialog.DefaultPixelWidth96Dpi * 5) / 4);

            if (clickedTB == flattenTB)
            {
                return DialogResult.Yes;
            }
            else
            {
                return DialogResult.No;
            }
        }
        
        private static string GetDefaultSaveName()
        {
            return PdnResources.GetString("Untitled.FriendlyName");
        }

        private static string GetDefaultSavePath()
        {
            string myPics;

            try
            {
                myPics = Shell.GetVirtualPath(VirtualFolderName.UserPictures, false);
                DirectoryInfo dirInfo = new DirectoryInfo(myPics); // validate
            }

            catch (Exception)
            {
                myPics = "";
            }

            string dir = Settings.CurrentUser.GetString(SettingNames.LastFileDialogDirectory, null);

            if (dir == null)
            {
                dir = myPics;
            }
            else
            {
                try
                {
                    DirectoryInfo dirInfo = new DirectoryInfo(dir);

                    if (!dirInfo.Exists)
                    {
                        dir = myPics;
                    }
                }

                catch (Exception)
                {
                    dir = myPics;
                }
            }

            return dir;
        }

        public bool DoSave()
        {
            return DoSave(false);
        }

        /// <summary>
        /// Does the dirty work for a File->Save operation. If any of the "Save Options" in the
        /// DocumentWorkspace are null, this will call DoSaveAs(). If the image has more than 1
        /// layer but the file type they want to save with does not support layers, then it will
        /// ask the user about flattening the image.
        /// </summary>
        /// <param name="tryToFlatten">
        /// If true, will ask the user about flattening if the workspace's saveFileType does not 
        /// support layers and the image has more than 1 layer.
        /// If false, then DoSaveAs will be called and the fileType will be prepopulated with
        /// the .PDN type.
        /// </param>
        /// <returns><b>true</b> if the file was saved, <b>false</b> if the user cancelled</returns>
        protected bool DoSave(bool tryToFlatten)
        {
            using (new PushNullToolMode(this))
            {
                string newFileName;
                FileType newFileType;
                SaveConfigToken newSaveConfigToken;

                GetDocumentSaveOptions(out newFileName, out newFileType, out newSaveConfigToken);

                // if they haven't specified a filename, then revert to "Save As" behavior
                if (newFileName == null)
                {
                    return DoSaveAs();
                }

                // if we have a filename but no file type, try to infer the file type
                if (newFileType == null)
                {
                    FileTypeCollection fileTypes = FileTypes.GetFileTypes();
                    string ext = Path.GetExtension(newFileName);
                    int index = fileTypes.IndexOfExtension(ext);
                    FileType inferredFileType = fileTypes[index];
                    newFileType = inferredFileType;
                }

                // if the image has more than 1 layer but is saving with a file type that
                // does not support layers, then we must ask them if we may flatten the
                // image first
                if (Document.Layers.Count > 1 && !newFileType.SupportsLayers)
                {
                    if (!tryToFlatten)
                    {
                        return DoSaveAs();
                    }
                    else
                    {
                        DialogResult dr = WarnAboutFlattening();

                        if (dr == DialogResult.Yes)
                        {
                            ExecuteFunction(new FlattenFunction());
                        }
                        else
                        {
                            return false;
                        }
                    }
                }

                // get the configuration!
                if (newSaveConfigToken == null)
                {
                    Surface scratch = BorrowScratchSurface(this.GetType().Name + ".DoSave() calling GetSaveConfigToken()");

                    bool result;
                    try
                    {
                        result = GetSaveConfigToken(newFileType, newSaveConfigToken, out newSaveConfigToken, scratch);
                    }

                    finally
                    {
                        ReturnScratchSurface(scratch);
                    }

                    if (!result)
                    {
                        return false;
                    }
                }

                // At this point fileName, fileType, and saveConfigToken must all be non-null

                // if the document supports custom headers, embed a thumbnail in there
                if (newFileType.SupportsCustomHeaders)
                {
                    using (new WaitCursorChanger(this))
                    {
                        Utility.GCFullCollect();
                        const int maxDim = 256;

                        Surface thumb;
                        Surface flattened = BorrowScratchSurface(this.GetType().Name + ".DoSave() preparing embedded thumbnail");

                        try
                        {
                            Document.Flatten(flattened);

                            if (Document.Width > maxDim || Document.Height > maxDim)
                            {
                                int width;
                                int height;

                                if (Document.Width > Document.Height)
                                {
                                    width = maxDim;
                                    height = (Document.Height * maxDim) / Document.Width;
                                }
                                else
                                {
                                    height = maxDim;
                                    width = (Document.Width * maxDim) / Document.Height;
                                }

                                int thumbWidth = Math.Max(1, width);
                                int thumbHeight = Math.Max(1, height);

                                thumb = new Surface(thumbWidth, thumbHeight);
                                thumb.SuperSamplingFitSurface(flattened);
                            }
                            else
                            {
                                thumb = new Surface(flattened.Size);
                                thumb.CopySurface(flattened);
                            }
                        }

                        finally
                        {
                            ReturnScratchSurface(flattened);
                        }

                        Document thumbDoc = new Document(thumb.Width, thumb.Height);
                        BitmapLayer thumbLayer = new BitmapLayer(thumb);
                        BitmapLayer backLayer = new BitmapLayer(thumb.Width, thumb.Height);
                        backLayer.Surface.Clear(ColorBgra.Transparent);
                        thumb.Dispose();
                        thumbDoc.Layers.Add(backLayer);
                        thumbDoc.Layers.Add(thumbLayer);
                        MemoryStream thumbPng = new MemoryStream();
                        PropertyBasedSaveConfigToken pngToken = PdnFileTypes.Png.CreateDefaultSaveConfigToken();
                        PdnFileTypes.Png.Save(thumbDoc, thumbPng, pngToken, null, null, false);
                        byte[] thumbBytes = thumbPng.ToArray();

                        string thumbString = Convert.ToBase64String(thumbBytes, Base64FormattingOptions.None);
                        thumbDoc.Dispose();

                        string thumbXml = "<thumb png=\"" + thumbString + "\" />";
                        Document.CustomHeaders = thumbXml;
                    }
                }

                // save!
                bool success = false;
                Stream stream = null;

                try
                {
                    stream = (Stream)new FileStream(newFileName, FileMode.Create, FileAccess.Write);

                    using (new WaitCursorChanger(this))
                    {
                        Utility.GCFullCollect();

                        SaveProgressDialog sd = new SaveProgressDialog(this);
                        Surface scratch = BorrowScratchSurface(this.GetType().Name + ".DoSave() handing off scratch surface to SaveProgressDialog.Save()");

                        try
                        {
                            sd.Save(stream, Document, newFileType, newSaveConfigToken, scratch);
                        }

                        finally
                        {
                            ReturnScratchSurface(scratch);
                        }

                        success = true;

                        this.lastSaveTime = DateTime.Now;

                        stream.Close();
                        stream = null;
                    }
                }

                catch (UnauthorizedAccessException)
                {
                    Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.UnauthorizedAccessException"));
                }

                catch (SecurityException)
                {
                    Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.SecurityException"));
                }

                catch (DirectoryNotFoundException)
                {
                    Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.DirectoryNotFoundException"));
                }

                catch (IOException)
                {
                    Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.IOException"));
                }

                catch (OutOfMemoryException)
                {
                    Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.OutOfMemoryException"));
                }

#if !DEBUG
                catch (Exception)
                {
                    Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.Exception"));
                }
#endif

                finally
                {
                    if (stream != null)
                    {
                        stream.Close();
                        stream = null;
                    }
                }

                if (success)
                {
                    Shell.AddToRecentDocumentsList(newFileName);
                }
                else
                {
                    return false;
                }

                // reset the dirty bit so they won't be asked to save on quitting
                Document.Dirty = false;

                // some misc. book keeping ...
                AddToMruList();

                // and finally, shout happiness by way of ...
                return true;
            }
        }

        /// <summary>
        /// Does the grunt work to do a File->Save As operation.
        /// </summary>
        /// <returns><b>true</b> if the file was saved correctly, <b>false</b> if the user cancelled</returns>
        public bool DoSaveAs()
        {
            using (new PushNullToolMode(this))
            {
                string newFileName;
                FileType newFileType;
                SaveConfigToken newSaveConfigToken;

                Surface scratch = BorrowScratchSurface(this.GetType() + ".DoSaveAs() handing off scratch surface to DoSaveAsDialog()");

                bool result;
                try
                {
                    result = DoSaveAsDialog(out newFileName, out newFileType, out newSaveConfigToken, scratch);
                }

                finally
                {
                    ReturnScratchSurface(scratch);
                }

                if (result)
                {
                    string oldFileName;
                    FileType oldFileType;
                    SaveConfigToken oldSaveConfigToken;

                    GetDocumentSaveOptions(out oldFileName, out oldFileType, out oldSaveConfigToken);
                    SetDocumentSaveOptions(newFileName, newFileType, newSaveConfigToken);

                    bool result2 = DoSave(true);

                    if (!result2)
                    {
                        SetDocumentSaveOptions(oldFileName, oldFileType, oldSaveConfigToken);
                    }

                    return result2;
                }
                else
                {
                    return false;
                }
            }
        }

        public static Document LoadDocument(Control owner, string fileName, out FileType fileTypeResult, ProgressEventHandler progressCallback)
        {
            FileTypeCollection fileTypes;
            int ftIndex;
            FileType fileType;

            fileTypeResult = null;

            try
            {
                fileTypes = FileTypes.GetFileTypes();
                ftIndex = fileTypes.IndexOfExtension(Path.GetExtension(fileName));

                if (ftIndex == -1)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.ImageTypeNotRecognized"));
                    return null;
                }

                fileType = fileTypes[ftIndex];
                fileTypeResult = fileType;
            }

            catch (ArgumentException)
            {
                string format = PdnResources.GetString("LoadImage.Error.InvalidFileName.Format");
                string error = string.Format(format, fileName);
                Utility.ErrorBox(owner, error);
                return null;
            }

            Document document = null;

            using (new WaitCursorChanger(owner))
            {
                Utility.GCFullCollect();
                Stream stream = null;

                try
                {
                    try
                    {
                        stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                        long totalBytes = 0;

                        SiphonStream siphonStream = new SiphonStream(stream);

                        IOEventHandler ioEventHandler = null;
                        ioEventHandler =
                            delegate(object sender, IOEventArgs e)
                            {
                                if (progressCallback != null)
                                {
                                    totalBytes += (long)e.Count;
                                    double percent = Utility.Clamp(100.0 * ((double)totalBytes / (double)siphonStream.Length), 0, 100);
                                    progressCallback(null, new ProgressEventArgs(percent));
                                }
                            };

                        siphonStream.IOFinished += ioEventHandler;

                        using (new WaitCursorChanger(owner))
                        {
                            document = fileType.Load(siphonStream);

                            if (progressCallback != null)
                            {
                                progressCallback(null, new ProgressEventArgs(100.0));
                            }
                        }

                        siphonStream.IOFinished -= ioEventHandler;
                        siphonStream.Close();
                    }

                    catch (WorkerThreadException ex)
                    {
                        Type innerExType = ex.InnerException.GetType();
                        ConstructorInfo ci = innerExType.GetConstructor(new Type[] { typeof(string), typeof(Exception) });

                        if (ci == null)
                        {
                            throw;
                        }
                        else
                        {
                            Exception ex2 = (Exception)ci.Invoke(new object[] { "Worker thread threw an exception of this type", ex.InnerException });
                            throw ex2;
                        }
                    }
                }

                catch (ArgumentException)
                {
                    if (fileName.Length == 0)
                    {
                        Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.BlankFileName"));
                    }
                    else
                    {
                        Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.ArgumentException"));
                    }
                }

                catch (UnauthorizedAccessException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.UnauthorizedAccessException"));
                }

                catch (SecurityException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.SecurityException"));
                }

                catch (FileNotFoundException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.FileNotFoundException"));
                }

                catch (DirectoryNotFoundException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.DirectoryNotFoundException"));
                }

                catch (PathTooLongException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.PathTooLongException"));
                }

                catch (IOException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.IOException"));
                }

                catch (SerializationException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.SerializationException"));
                }

                catch (OutOfMemoryException)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.OutOfMemoryException"));
                }

                catch (Exception)
                {
                    Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.Exception"));
                }

                finally
                {
                    if (stream != null)
                    {
                        stream.Close();
                        stream = null;
                    }
                }
            }

            return document;
        }

        public Surface RenderThumbnail(int maxEdgeLength, bool highQuality, bool forceUpToDate)
        {
            if (Document == null)
            {
                Surface ret = new Surface(maxEdgeLength, maxEdgeLength);
                ret.Clear(ColorBgra.Transparent);
                return ret;
            }

            Size thumbSize = Utility.ComputeThumbnailSize(Document.Size, maxEdgeLength);
            Surface thumb = new Surface(thumbSize);
            thumb.Clear(ColorBgra.Transparent);

            RenderCompositionTo(thumb, highQuality, forceUpToDate);

            return thumb;
        }

        Surface IThumbnailProvider.RenderThumbnail(int maxEdgeLength)
        {
            return RenderThumbnail(maxEdgeLength, true, false);
        }
    }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.