File: src\Framework\System\Windows\Controls\PrintDialog.cs
Project: wpf\PresentationFramework.csproj (PresentationFramework)
#if !DONOTREFPRINTINGASMMETA
/*++
 
    Copyright (C) 2004 - 2005 Microsoft Corporation.
    All rights reserved.
 
    Module Name:
        PrintDialog.cs
 
    Abstract:
        This file contains the implementation of the PrintDialog class
        and its supporting enums.
 
    Author:
        Robert Anderson (robertan) 9-May-2005
 
--*/
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Printing;
using System.Security;
using System.Security.Permissions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Xps;
using MS.Internal.Printing;
using System.Windows.Xps.Serialization;
using System.Windows.Documents;
using System.Windows.Documents.Serialization;  // WritingCompletedEventArgs
using MS.Internal.PresentationFramework;
 
namespace System.Windows.Controls
{
    /// <summary>
    /// This class is used to show a print which will configure a
    /// user PrintTicket and Printer based on user input.  This
    /// PrintTicket and Printer is then used to perform a print job.
    /// </summary>
    public class PrintDialog
    {
        #region Constructors
 
        /// <summary>
        /// Instantiates an instance of the Print Dialog.
        /// </summary>
        /// <SecurityNote>
        ///     Critical:    - setting critical data (_printQueue, _printTicket, _dialogInvoked)
        ///     PublicOk:    - We are setting these to "known" values of null.  There
        ///                    is no data exposure here.
        /// </SecurityNote>
        [SecurityCritical]
        public
        PrintDialog(
            )
        {
            _dialogInvoked = false;
 
            _printQueue = null;
            _printTicket = null;
 
            _isPrintableAreaWidthUpdated  = false;
            _isPrintableAreaHeightUpdated = false;
 
            _pageRangeSelection = PageRangeSelection.AllPages;
            _minPage = 1;
            _maxPage = 9999;
            _userPageRangeEnabled = false;
        }
 
        #endregion Constructors
 
        #region Public properties
 
        /// <summary>
        /// Gets or Sets the PageRangeSelection option for the print dialog.
        /// </summary>
        public PageRangeSelection PageRangeSelection
        {
            get
            {
                return _pageRangeSelection;
            }
            set
            {
                _pageRangeSelection = value;
            }
        }
 
        /// <summary>
        /// Gets or sets a PageRange objects used when the PageRangeSelection
        /// option is set to UserPages.
        /// </summary>
        public PageRange PageRange
        {
            get
            {
                return _pageRange;
            }
            set
            {
                if ((value.PageTo <= 0) || (value.PageFrom <= 0))
                {
                    throw new System.ArgumentException(SR.Get(SRID.PrintDialogInvalidPageRange), "PageRange");
                }
 
                _pageRange = value;
 
                if (_pageRange.PageFrom > _pageRange.PageTo)
                {
                    int temp = _pageRange.PageFrom;
                    _pageRange.PageFrom = _pageRange.PageTo;
                    _pageRange.PageTo = temp;
                }
            }
        }
 
        /// <summary>
        /// Gets or a sets a flag to enable/disable the user page range support on
        /// the print dialog.
        /// </summary>
        public bool UserPageRangeEnabled
        {
            get
            {
                return _userPageRangeEnabled;
            }
            set
            {
                _userPageRangeEnabled = value;
            }
        }
 
        /// <summary>
        /// Gets or a sets a flag to enable/disable the selected pages support on
        /// the print dialog.
        /// </summary>
        public bool SelectedPagesEnabled
        {
            get
            {
                return _selectedPagesEnabled;
            }
            set
            {
                _selectedPagesEnabled = value;
            }
        }
 
        /// <summary>
        /// Gets or a sets a flag to enable/disable the current page selection support on
        /// the print dialog.
        /// </summary>
        public bool CurrentPageEnabled
        {
            get
            {
                return _currentPageEnabled;
            }
            set
            {
                _currentPageEnabled = value;
            }
        }
 
        // the following two properties return non CLS-compliant type UInt32 (bug 1788246)
        #pragma warning disable 3003
 
        /// <summary>
        /// Gets or sets the minimum page number allowed in the page ranges.
        /// </summary>
        public UInt32 MinPage
        {
            get
            {
                return _minPage;
            }
            set
            {
                if (_minPage <= 0)
                {
                    throw new System.ArgumentException(SR.Get(SRID.PrintDialogZeroNotAllowed, "MinPage"));
                }
 
                _minPage = value;
            }
        }
 
        /// <summary>
        /// Gets or sets the maximum page number allowed in the page ranges.
        /// </summary>
        public UInt32 MaxPage
        {
            get
            {
                return _maxPage;
            }
            set
            {
                if (_maxPage <= 0)
                {
                    throw new System.ArgumentException(SR.Get(SRID.PrintDialogZeroNotAllowed, "MaxPage"));
                }
 
                _maxPage = value;
            }
        }
 
        #pragma warning restore 3003
 
        /// <summary>
        /// Gets or sets the printer selection.
        /// </summary>
        /// <SecurityNote>
        ///     Critical:    - The getter is critical since it accesses critical data in web application mode.
        ///                  - The getter is calling critical code (AcquireDefaultPrintQueue).
        ///                  - The setter is critical since it is returning critical data.
        ///     PublicOk:    - A demand is made for default printing before returning the print queue.  This
        ///                    would be the same permission required to get the print queue in the first place.
        ///                  - We also demand before setting the print queue object.  If they can satisfy the
        ///                    the demand then they are safe to print anyways.
        /// </SecurityNote>
        public PrintQueue PrintQueue
        {
            [SecurityCritical]
            get
            {
                SecurityHelper.DemandPrintDialogPermissions();
 
                if (_printQueue == null)
                {
                    _printQueue = AcquireDefaultPrintQueue();
                }
 
                return _printQueue;
            }
            [SecurityCritical]
            set
            {
                SecurityHelper.DemandPrintDialogPermissions();
 
                _printQueue = value;
            }
        }
 
        /// <summary>
        /// Get or sets the current PrintTicket object.
        /// </summary>
        /// <SecurityNote>
        ///     Critical:    - The getter is critical since it accesses critical data.
        ///                  - The getter is calling critical code (AcquireDefaultPrintTicket).
        ///                  - The setter is critical since it is returning critical data.
        ///     PublicOk:    - A demand is made for default printing before returning the print ticket.  This
        ///                    would be the same permission required to get the print ticket in the first place.
        ///                  - We also demand before setting the print ticket object.  If they can satisfy the
        ///                    the demand then they are safe to print anyways.
        /// </SecurityNote>
        public PrintTicket PrintTicket
        {
            [SecurityCritical]
            get
            {
                SecurityHelper.DemandPrintDialogPermissions();
 
                if (_printTicket == null)
                {
                    _printTicket = AcquireDefaultPrintTicket(this.PrintQueue);
                }
 
                return _printTicket;
            }
            [SecurityCritical]
            set
            {
                SecurityHelper.DemandPrintDialogPermissions();
 
                _printTicket = value;
            }
        }
 
        /// <summary>
        /// Get the Width of the area on paper to which the application can print
        /// </summary>
        public
        double
        PrintableAreaWidth
        {
            get
            {
                if( ((_isPrintableAreaWidthUpdated == false) && (_isPrintableAreaHeightUpdated == false)) ||
                    ((_isPrintableAreaWidthUpdated == true)  && (_isPrintableAreaHeightUpdated == false)))
                {
                    _isPrintableAreaWidthUpdated  = true;
                    _isPrintableAreaHeightUpdated = false;
 
                    UpdatePrintableAreaSize();
                }
 
                return _printableAreaWidth;
            }
        }
 
        /// <summary>
        /// Get the Height of the area on paper to which the application can print
        /// </summary>
        public
        double
        PrintableAreaHeight
        {
            get
            {
                if( ((_isPrintableAreaWidthUpdated == false) && (_isPrintableAreaHeightUpdated == false)) ||
                    ((_isPrintableAreaWidthUpdated == false)  && (_isPrintableAreaHeightUpdated == true)))
                {
                    _isPrintableAreaWidthUpdated  = false;
                    _isPrintableAreaHeightUpdated = true;
 
                    UpdatePrintableAreaSize();
                }
                return _printableAreaHeight;
            }
        }
 
 
        #endregion Public properties
 
        #region Public methods
 
        /// <summary>
        /// Pops the dialog up to the user in a modal form.
        /// </summary>
        /// <SecurityNote>
        ///     Critical:    - Accesses and sets critical data.
        ///     PublicOk:    - Data is internal to this dialog and can only be retrieved
        ///                    by other critical code.  No information leaves this method.
        /// </SecurityNote>
        [SecurityCritical]
        public
        Nullable<bool>
        ShowDialog()
        {
            //
            // Reset this flag as we have not displayed the dialog yet.
            //
            _dialogInvoked = false;
 
            Win32PrintDialog dlg = new Win32PrintDialog();
 
            //
            // Setup the old values if any exist.
            //
            dlg.PrintTicket = _printTicket;
            dlg.PrintQueue = _printQueue;
            dlg.MinPage = Math.Max(1, Math.Min(_minPage, _maxPage));
            dlg.MaxPage = Math.Max(dlg.MinPage, Math.Max(_minPage, _maxPage));
            dlg.PageRangeEnabled = _userPageRangeEnabled;
            dlg.SelectedPagesEnabled = _selectedPagesEnabled;
            dlg.CurrentPageEnabled = _currentPageEnabled;
 
            dlg.PageRange = new PageRange(
                Math.Max((int)dlg.MinPage, _pageRange.PageFrom),
                Math.Min((int)dlg.MaxPage, _pageRange.PageTo));
            dlg.PageRangeSelection = _pageRangeSelection;
 
            //
            // Invoke the Win32 dialog
            //
            UInt32 dialogResult = dlg.ShowDialog();
 
            if ((dialogResult == MS.Internal.Printing.NativeMethods.PD_RESULT_APPLY) ||
                (dialogResult == MS.Internal.Printing.NativeMethods.PD_RESULT_PRINT))
            {
                _printTicket = dlg.PrintTicket;
                _printQueue = dlg.PrintQueue;
                _pageRange = dlg.PageRange;
                _pageRangeSelection = dlg.PageRangeSelection;
                _dialogInvoked = true;
            }
 
            return (dialogResult == MS.Internal.Printing.NativeMethods.PD_RESULT_PRINT);
        }
 
        /// <summary>
        /// Prints a Visual to the currently selected Print Queue.
        /// </summary>
        /// <param name="visual">
        /// The visual to be printed.
        /// </param>
        /// <param name="description">
        /// Description of the job to be printed. This shows in the Printer UI
        /// </param>
        /// <SecurityNote>
        ///     Critical:    - Sets a critical data property.
        ///     PublicOk:    - The critical data is a flag that needs to be reset for each print
        ///                    job to enforce the dialog invocation on every print operation.  Without
        ///                    this method resetting this flag we would not be honoring the security
        ///                    goal of displaying the dialog once per print job.
        /// </SecurityNote>
        [SecurityCritical]
        public
        void
        PrintVisual(
            Visual visual,
            String description
            )
        {
            if (visual == null)
            {
                throw new ArgumentNullException("visual");
            }
 
            XpsDocumentWriter writer = CreateWriter(description);
 
            writer.Write(visual, _printTicket);
 
            _printableAreaWidth             = 0;
            _printableAreaHeight            = 0;
            _isPrintableAreaWidthUpdated    = false;
            _isPrintableAreaHeightUpdated   = false;
            _dialogInvoked                  = false;
        }
 
 
        /// <summary>
        /// Prints an DocumentPaginator based document to the currently selected Print Queue.
        /// </summary>
        /// <param name="documentPaginator">
        /// The DocumentPaginator to be printed.
        /// </param>
        /// <param name="description">
        /// Description of the job to be printed. This shows in the Printer UI
        /// </param>
        /// <SecurityNote>
        ///     Critical:    - Sets a critical data property.
        ///     PublicOk:    - The critical data is a flag that needs to be reset for each print
        ///                    job to enforce the dialog invocation on every print operation.  Without
        ///                    this method resetting this flag we would not be honoring the security
        ///                    goal of displaying the dialog once per print job.
        /// </SecurityNote>
        [SecurityCritical]
        public
        void
        PrintDocument(
            DocumentPaginator  documentPaginator,
            String             description
            )
        {
            if (documentPaginator == null)
            {
                throw new ArgumentNullException("documentPaginator");
            }
 
            XpsDocumentWriter writer = CreateWriter(description);
 
            writer.Write(documentPaginator, _printTicket);
 
            _printableAreaWidth = 0;
            _printableAreaHeight = 0;
            _isPrintableAreaWidthUpdated = false;
            _isPrintableAreaHeightUpdated = false;
            _dialogInvoked = false;
        }
 
        #endregion Public methods
 
        #region Private methods
 
        /// <SecurityNote>
        ///     Critical:    - Asserts to obtain the default print queue from the local server.
        /// </SecurityNote>
        [SecurityCritical]
        private
        PrintQueue
        AcquireDefaultPrintQueue()
        {
            PrintQueue printQueue = null;
 
            MS.Internal.SystemDrawingHelper.NewDefaultPrintingPermission().Assert(); //BlessedAssert
            try
            {
                try
                {
                    LocalPrintServer server = new LocalPrintServer();
                    printQueue = server.DefaultPrintQueue;
                }
                catch (PrintSystemException)
                {
                    //
                    // It is entirely possible for there to be no "default" printer.  In this case,
                    // the printing system throws an exception.  We do not want this to propagate
                    // up.  Instead, returning null is fine.
                    //
                    printQueue = null;
                }
            }
            finally
            {
                CodeAccessPermission.RevertAssert();
            }
 
            return printQueue;
        }
 
        /// <SecurityNote>
        ///     Critical:    - Asserts to obtain the PrintTicket from the specified PrintQueue
        ///                    object or create a blank PrintTicket object (i.e. PrintTicket::ctor).
        /// </SecurityNote>
        [SecurityCritical]
        private
        PrintTicket
        AcquireDefaultPrintTicket(
            PrintQueue printQueue
            )
        {
            PrintTicket printTicket = null;
 
            MS.Internal.SystemDrawingHelper.NewDefaultPrintingPermission().Assert(); //BlessedAssert
            try
            {
                try
                {
                    if (printQueue != null)
                    {
                        printTicket = printQueue.UserPrintTicket;
                        if (printTicket == null)
                        {
                            printTicket = printQueue.DefaultPrintTicket;
                        }
                    }
                }
                catch (PrintSystemException)
                {
                    //
                    // The printing subsystem can throw an exception in certain cases when
                    // the print ticket is unavailable.  If it does we will handle this
                    // below.  There is no real need to bubble this up to the application.
                    //
                    printTicket = null;
                }
            }
            finally
            {
                CodeAccessPermission.RevertAssert();
            }
 
            //
            // If the printing subsystem could not give us a print ticket either due to
            // a failure or because a user/system default was not available, then just
            // create a blank/empty one.
            //
            if (printTicket == null)
            {
                printTicket = new PrintTicket();
            }
 
            return printTicket;
        }
 
        /// <SecurityNote>
        ///     Critical:    - Invokes a critical method (PickCorrectPrintingEnvironment).
        ///     TreatAsSafe: - Critical data returned from above method is internal and does
        ///                    not leave the scope of this method.  It is only used to calculate
        ///                    non-critical values.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private
        void
        UpdatePrintableAreaSize(
            )
        {
            PrintQueue  printQueue  = null;
            PrintTicket printTicket = null;
 
            PickCorrectPrintingEnvironment(ref printQueue, ref printTicket);
 
            PrintCapabilities printCap = null;
            if (printQueue != null)
            {
                printCap = printQueue.GetPrintCapabilities(printTicket);
            }
 
            // PrintCapabilities OrientedPageMediaWidth/Height are Nullable
            if ((printCap != null) &&
                (printCap.OrientedPageMediaWidth != null) &&
                (printCap.OrientedPageMediaHeight != null))
            {
                _printableAreaWidth  = (double)printCap.OrientedPageMediaWidth;
                _printableAreaHeight = (double)printCap.OrientedPageMediaHeight;
            }
            else
            {
                // Initialize page size to portrait Letter size.
                // This is our fallback if PrintTicket doesn't specify the page size.
                _printableAreaWidth = 816;
                _printableAreaHeight = 1056;
 
                // PrintTicket's PageMediaSize could be null and PageMediaSize Width/Height are Nullable
 
                if ((printTicket.PageMediaSize != null) &&
                    (printTicket.PageMediaSize.Width != null) &&
                    (printTicket.PageMediaSize.Height != null))
                {
                    _printableAreaWidth  = (double)printTicket.PageMediaSize.Width;
                    _printableAreaHeight = (double)printTicket.PageMediaSize.Height;
                }
 
                // If we are using PrintTicket's PageMediaSize dimensions to populate the widht/height values,
                // we need to adjust them based on current orientation. PrintTicket's PageOrientation is Nullable.
                if (printTicket.PageOrientation != null)
                {
                    PageOrientation orientation = (PageOrientation)printTicket.PageOrientation;
 
                    // need to swap width/height in landscape orientation
                    if ((orientation == PageOrientation.Landscape) ||
                        (orientation == PageOrientation.ReverseLandscape))
                    {
                        double t = _printableAreaWidth;
                        _printableAreaWidth  = _printableAreaHeight;
                        _printableAreaHeight = t;
                    }
                }
            }
        }
 
        /// <SecurityNote>
        ///     Critical:    - Asserts for PrintingPermissionLevel.DefaultPrinting
        ///                    to be able to use the printQueue to create the
        ///                    XpsDocumentWriter.
        ///     TreatAsSafe: - The assert is only done after ensuring that the user
        ///                    has conciously made a decision to print by successfully
        ///                    dismissing the Print Dialog.  This logic of a dialog
        ///                    being required is only needed for partial trust applications.
        ///                    The logic of checking this criteria is contained within the
        ///                    PickCorrectPrintingEnvironment method.
        ///                  - The XpsDocumentWriter instance returned from this method
        ///                    is not unsafe since the application is either full trust
        ///                    or the user chose to print.  It is okay for the application
        ///                    to use the XpsDocumentWriter to print at this point.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        private
        XpsDocumentWriter
        CreateWriter(
            String  description
            )
        {
            PrintQueue        printQueue  = null;
            PrintTicket       printTicket = null;
            XpsDocumentWriter writer      = null;
 
            PickCorrectPrintingEnvironment(ref printQueue, ref printTicket);
 
            MS.Internal.SystemDrawingHelper.NewDefaultPrintingPermission().Assert(); //BlessedAssert
            try
            {
                if(printQueue != null)
                {
                    printQueue.CurrentJobSettings.Description = description;
                }
 
                writer = PrintQueue.CreateXpsDocumentWriter(printQueue);
 
                PrintDlgPrintTicketEventHandler eventHandler = new PrintDlgPrintTicketEventHandler(printTicket);
 
                writer.WritingPrintTicketRequired +=
                new WritingPrintTicketRequiredEventHandler(eventHandler.SetPrintTicket);
            }
            finally
            {
                CodeAccessPermission.RevertAssert();
            }
 
            return writer;
        }
 
        /// <SecurityNote>
        ///     Critical:    - Accesses critical data and returns it to the caller.
        ///                  - Calls critical code (AcquireDefaultPrintQueue/AcquireDefaultPrintTicket)
        ///                  - Detects whether a caller is allowed to acquire this data
        ///                    based on a demand.  This demand is only performed if the
        ///                    dialog was not invoked already.  It is fine to return the
        ///                    data if the dialog was invoked, however, the data is still
        ///                    critical.
        ///
        /// NOTE:  This method validates that a dialog was invoked prior to returning the
        ///        PrintQueue and PrintTicket for the case of web applications.  If the
        ///        dialog was not invoked then an exception is thrown.
        /// </SecurityNote>
        [SecurityCritical]
        private
        void
        PickCorrectPrintingEnvironment(
            ref PrintQueue  printQueue,
            ref PrintTicket printTicket
            )
        {
            if (_dialogInvoked == false)
            {
                //
                // If the dialog has not been invoked then the user needs printing permissions.
                // If the demand succeeds then they can print.  If the demand fails, then we
                // tell them that the print dialog must be displayed first by throwing a dialog
                // exception.
                //
                try
                {
                    SecurityHelper.DemandPrintDialogPermissions();
                }
                catch (SecurityException)
                {
                    throw new PrintDialogException(SR.Get(SRID.PartialTrustPrintDialogMustBeInvoked));
                }
            }
 
            //
            // If the default print queue and print ticket have not already
            // been selected then update them now since we need them.
            //
            // NOTE:  If this code gets called then we know the dialog has never
            //        been invoked but the above demand was satisfied.  In this
            //        case we want to just pickup the user defaults.
            //
            if (_printQueue == null)
            {
                _printQueue = AcquireDefaultPrintQueue();
            }
            if (_printTicket == null)
            {
                _printTicket = AcquireDefaultPrintTicket(_printQueue);
            }
 
            //
            // We should have valid print queue and print ticket objects to
            // return now.  As a note, a null PrintQueue is valid for this
            // since the dialog will automatically pick up the user default
            // printer for us.
            //
            printQueue = _printQueue;
            printTicket = _printTicket;
        }
 
        #endregion Private methods
 
        #region Private data
 
        /// <SecurityNote>
        /// The PrintTicket is critical and not obtainable from a partial
        /// trust application unless they can satisfy a printing permission
        /// demand.
        /// </SecurityNote>
        [SecurityCritical]
        private
        PrintTicket                 _printTicket;
 
        /// <SecurityNote>
        /// The PrintQueue is critical and not obtainable from a partial
        /// trust application unless they can satisfy a printing permission
        /// demand.
        /// </SecurityNote>
        [SecurityCritical]
        private
        PrintQueue                  _printQueue;
 
        /// <SecurityNote>
        /// This variable is used to determine whether a user actually invoked
        /// and dismissed the dialog prior to printing.  In a partial trust app,
        /// we can safely perform the necessary asserts to print as long as the
        /// user said printing was okay.
        /// </SecurityNote>
        [SecurityCritical]
        private
        bool                        _dialogInvoked;
 
        private
        PageRangeSelection          _pageRangeSelection;
 
        private
        PageRange                   _pageRange;
 
        private
        bool                        _userPageRangeEnabled;
 
        private
        bool                        _selectedPagesEnabled;
 
        private
        bool                        _currentPageEnabled;        
 
        private
        UInt32                      _minPage;
 
        private
        UInt32                      _maxPage;
 
        private
        double                      _printableAreaWidth;
 
        private
        double                      _printableAreaHeight;
 
        private
        bool                        _isPrintableAreaWidthUpdated;
 
        private
        bool                        _isPrintableAreaHeightUpdated;
 
        #endregion Private data
 
        #region Internal classes
 
        internal class PrintDlgPrintTicketEventHandler
        {
            #region Constructor
 
            /// <SecurityNote>
            ///     Critical    -   PrintTicket argument is critical because it is defined in the none APTCA assembly ReachFramework.dll
            ///     TreatAsSafe -   PrintTicket type is safe
            /// </SecurityNote>
            [SecurityCritical, SecurityTreatAsSafe]
            public
            PrintDlgPrintTicketEventHandler(
                PrintTicket printTicket
                )
            {
                _printTicket = printTicket;
            }
 
            #endregion Constructor
 
            #region Public Methods
 
            /// <SecurityNote>
            ///     Critical    -   Makes use of PrintTicket type which is critical because it is defined in the none APTCA assembly ReachFramework.dll
            ///                 -   Makes use of PrintTicketLevel type which is critical because it is defined in the none APTCA assembly ReachFramework.dll
            ///     TreatAsSafe -   PrintTicket type is safe
            /// </SecurityNote>
            [SecurityCritical, SecurityTreatAsSafe]
            public
            void
            SetPrintTicket(
                Object sender,
                WritingPrintTicketRequiredEventArgs args
                )
            {
                if (args.CurrentPrintTicketLevel == PrintTicketLevel.FixedDocumentSequencePrintTicket)
                {
                    args.CurrentPrintTicket = _printTicket;
                }
            }
 
            #endregion Public Methods
 
            #region Private Data
 
            /// <SecurityNote>
            ///     Critical    -   Field for PrintTicket type which is critical because it is defined in the none APTCA assembly ReachFramework.dll
            /// </SecurityNote>
            [SecurityCritical]
            private
            PrintTicket _printTicket;
 
            #endregion Private Data
        };
 
        #endregion Internal classes
    }
}
#endif