File: winforms\Managed\System\WinForms\FontDialog.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="FontDialog.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms {
 
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
 
    using System;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Security;
    using System.Security.Permissions;
    using System.Runtime.Versioning;
    using Microsoft.Win32;
 
    /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Represents
    ///       a common dialog box that displays a list of fonts that are currently installed
    ///       on
    ///       the system.
    ///    </para>
    /// </devdoc>
    [
    DefaultEvent("Apply"),
    DefaultProperty("Font"),
    SRDescription(SR.DescriptionFontDialog)
    ]
    public class FontDialog : CommonDialog {
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.EventApply"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        protected static readonly object EventApply = new object();
 
        private const int defaultMinSize = 0;
        private const int defaultMaxSize = 0;
 
        private int options;
        private Font font;
        private Color color;
        private int minSize = defaultMinSize;
        private int maxSize = defaultMaxSize;
        private bool showColor = false;
        private bool usingDefaultIndirectColor = false;
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.FontDialog"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.Windows.Forms.FontDialog'/>
        ///       class.
        ///    </para>
        /// </devdoc>
        [
            SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")  // If the constructor does not call Reset
                                                                                                    // it would be a breaking change.
        ]
        public FontDialog() {
            Reset();
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.AllowSimulations"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box allows graphics device interface
        ///       (GDI) font simulations.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(true),
        SRDescription(SR.FnDallowSimulationsDescr)
        ]
        public bool AllowSimulations {
            get {
                return !GetOption(NativeMethods.CF_NOSIMULATIONS);
            }
 
            set {
                SetOption(NativeMethods.CF_NOSIMULATIONS, !value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.AllowVectorFonts"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box allows vector font selections.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(true),
        SRDescription(SR.FnDallowVectorFontsDescr)
        ]
        public bool AllowVectorFonts {
            get {
                return !GetOption(NativeMethods.CF_NOVECTORFONTS);
            }
 
            set {
                SetOption(NativeMethods.CF_NOVECTORFONTS, !value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.AllowVerticalFonts"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether
        ///       the dialog box displays both vertical and horizontal fonts or only
        ///       horizontal fonts.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(true),
        SRDescription(SR.FnDallowVerticalFontsDescr)
        ]
        public bool AllowVerticalFonts {
            get {
                return !GetOption(NativeMethods.CF_NOVERTFONTS);
            }
 
            set {
                SetOption(NativeMethods.CF_NOVERTFONTS, !value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.AllowScriptChange"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets
        ///       or sets a value indicating whether the user can change the character set specified
        ///       in the Script combo box to display a character set other than the one
        ///       currently displayed.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(true),
        SRDescription(SR.FnDallowScriptChangeDescr)
        ]
        public bool AllowScriptChange {
            get {
                return !GetOption(NativeMethods.CF_SELECTSCRIPT);
            }
 
            set {
                SetOption(NativeMethods.CF_SELECTSCRIPT, !value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.Color"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating the selected font color.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatData), 
        SRDescription(SR.FnDcolorDescr),
        DefaultValue(typeof(Color), "Black")
        ]
        public Color Color {
            get {
                // Convert to RGB and back to resolve indirect colors like SystemColors.ControlText
                // to real color values like Color.Lime
                if (usingDefaultIndirectColor) {
                    return ColorTranslator.FromWin32(ColorTranslator.ToWin32(color));
                }
                return color;
            }
            set {
                if (!value.IsEmpty) {
                    color = value;
                    usingDefaultIndirectColor = false;
                }
                else {
                    color = SystemColors.ControlText;
                    usingDefaultIndirectColor = true;
                }
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.FixedPitchOnly"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets
        ///       a value indicating whether the dialog box allows only the selection of fixed-pitch fonts.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(false),
        SRDescription(SR.FnDfixedPitchOnlyDescr)
        ]
        public bool FixedPitchOnly {
            get {
                return GetOption(NativeMethods.CF_FIXEDPITCHONLY);
            }
 
            set {
                SetOption(NativeMethods.CF_FIXEDPITCHONLY, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.Font"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating the selected font.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatData), 
        SRDescription(SR.FnDfontDescr)
        ]
        public Font Font {
            [ResourceExposure(ResourceScope.Process)]
            [ResourceConsumption(ResourceScope.Process)]
            get {
                Font result = font;
                if (result == null)
                    result = Control.DefaultFont;
 
                float actualSize =  result.SizeInPoints;
                if (minSize != defaultMinSize && actualSize < MinSize)
                    result = new Font(result.FontFamily, MinSize, result.Style, GraphicsUnit.Point);
                if (maxSize != defaultMaxSize && actualSize > MaxSize)
                    result = new Font(result.FontFamily, MaxSize, result.Style, GraphicsUnit.Point);
 
                return result;
            }
            set {
                font = value;
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.FontMustExist"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box specifies an error condition if the
        ///       user attempts to select a font or style that does not exist.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(false),
        SRDescription(SR.FnDfontMustExistDescr)
        ]
        public bool FontMustExist {
            get {
                return GetOption(NativeMethods.CF_FORCEFONTEXIST);
            }
 
            set {
                SetOption(NativeMethods.CF_FORCEFONTEXIST, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.MaxSize"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets the maximum
        ///       point size a user can select.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatData), 
        DefaultValue(defaultMaxSize),
        SRDescription(SR.FnDmaxSizeDescr)
        ]
        public int MaxSize {
            get {
                return maxSize;
            }
            set {
                if (value < 0) {
                    value = 0;
                }
                maxSize = value;
 
                if (maxSize > 0 && maxSize < minSize) {
                    minSize = maxSize;
                }
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.MinSize"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating the minimum point size a user can select.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatData), 
        DefaultValue(defaultMinSize),
        SRDescription(SR.FnDminSizeDescr)
        ]
        public int MinSize {
            get {
                return minSize;
            }
            set {
                if (value < 0) {
                    value = 0;
                }
                minSize = value;
 
                if (maxSize > 0 && maxSize < minSize) {
                    maxSize = minSize;
                }
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.Options"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>
        ///       Gets the value passed to CHOOSEFONT.Flags.
        ///    </para>
        /// </devdoc>
        protected int Options {
            get {
                return options;
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ScriptsOnly"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a
        ///       value indicating whether the dialog box allows selection of fonts for all non-OEM and Symbol character
        ///       sets, as well as the ----n National Standards Institute (ANSI) character set.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(false),
        SRDescription(SR.FnDscriptsOnlyDescr)
        ]
        public bool ScriptsOnly {
            get {
                return GetOption(NativeMethods.CF_SCRIPTSONLY);
            }
            set {
                SetOption(NativeMethods.CF_SCRIPTSONLY, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ShowApply"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box contains an Apply button.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(false),
        SRDescription(SR.FnDshowApplyDescr)
        ]
        public bool ShowApply {
            get {
                return GetOption(NativeMethods.CF_APPLY);
            }
            set {
                SetOption(NativeMethods.CF_APPLY, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ShowColor"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box displays the color choice.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(false),
        SRDescription(SR.FnDshowColorDescr)
        ]
        public bool ShowColor {
            get {
                return showColor;
            }
            set {
                this.showColor = value;
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ShowEffects"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box contains controls that allow the
        ///       user to specify strikethrough, underline, and text color options.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(true),
        SRDescription(SR.FnDshowEffectsDescr)
        ]
        public bool ShowEffects {
            get {
                return GetOption(NativeMethods.CF_EFFECTS);
            }
            set {
                SetOption(NativeMethods.CF_EFFECTS, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ShowHelp"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets or sets a value indicating whether the dialog box displays a Help button.
        ///    </para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior), 
        DefaultValue(false),
        SRDescription(SR.FnDshowHelpDescr)
        ]
        public bool ShowHelp {
            get {
                return GetOption(NativeMethods.CF_SHOWHELP);
            }
            set {
                SetOption(NativeMethods.CF_SHOWHELP, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.Apply"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Occurs when the user clicks the Apply button in the font
        ///       dialog box.
        ///    </para>
        /// </devdoc>
        [SRDescription(SR.FnDapplyDescr)]
        public event EventHandler Apply {
            add {
                Events.AddHandler(EventApply, value);
            }
            remove {
                Events.RemoveHandler(EventApply, value);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.GetOption"]/*' />
        /// <devdoc>
        ///     Returns the state of the given option flag.
        /// </devdoc>
        /// <internalonly/>
        internal bool GetOption(int option) {
            return(options & option) != 0;
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.HookProc"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Specifies the common dialog box hook procedure that is overridden to add
        ///       specific functionality to a common dialog box.
        ///    </para>
        /// </devdoc>
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] 
        protected override IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) {
            
            switch (msg) {
                case NativeMethods.WM_COMMAND:
                    if ((int)wparam == 0x402) {
                        NativeMethods.LOGFONT lf = new NativeMethods.LOGFONT();
                        UnsafeNativeMethods.SendMessage(new HandleRef(null, hWnd), NativeMethods.WM_CHOOSEFONT_GETLOGFONT, 0, lf);
                        UpdateFont(lf);
                        int index = (int)UnsafeNativeMethods.SendDlgItemMessage(new HandleRef(null, hWnd), 0x473, NativeMethods.CB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
                        if (index != NativeMethods.CB_ERR) {
                            UpdateColor((int)UnsafeNativeMethods.SendDlgItemMessage(new HandleRef(null, hWnd), 0x473,
                                                                         NativeMethods.CB_GETITEMDATA, (IntPtr) index, IntPtr.Zero));
                        }
                        if (NativeWindow.WndProcShouldBeDebuggable) {
                            OnApply(EventArgs.Empty);
                        }
                        else {
                            try
                            {
                                OnApply(EventArgs.Empty);
                            }
                            catch (Exception e)
                            {
                                Application.OnThreadException(e);
                            }
                        }
                    }
                    break;
                case NativeMethods.WM_INITDIALOG:
                    if (!showColor) {
                        IntPtr hWndCtl = UnsafeNativeMethods.GetDlgItem(new HandleRef(null, hWnd), NativeMethods.cmb4);
                        SafeNativeMethods.ShowWindow(new HandleRef(null, hWndCtl), NativeMethods.SW_HIDE);
                        hWndCtl = UnsafeNativeMethods.GetDlgItem(new HandleRef(null, hWnd), NativeMethods.stc4);
                        SafeNativeMethods.ShowWindow(new HandleRef(null, hWndCtl), NativeMethods.SW_HIDE);
                    }
                    break;
            }
            
            return base.HookProc(hWnd, msg, wparam, lparam);
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.OnApply"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Raises the <see cref='System.Windows.Forms.FontDialog.Apply'/> event.
        ///    </para>
        /// </devdoc>
        protected virtual void OnApply(EventArgs e) {
            EventHandler handler = (EventHandler)Events[EventApply];
            if (handler != null) handler(this, e);
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.Reset"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Resets all dialog box options to their default values.
        ///    </para>
        /// </devdoc>
        public override void Reset() {
            options = NativeMethods.CF_SCREENFONTS | NativeMethods.CF_EFFECTS;
            font = null;
            color = SystemColors.ControlText;
            usingDefaultIndirectColor = true; 
            showColor = false;
            minSize = defaultMinSize;
            maxSize = defaultMaxSize;
            SetOption(NativeMethods.CF_TTONLY, true);
        }
 
        private void ResetFont() {
            font = null;
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.RunDialog"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>
        ///       The actual implementation of running the dialog. Inheriting classes
        ///       should override this if they want to add more functionality, and call
        ///       base.runDialog() if necessary
        ///       
        ///    </para>
        /// </devdoc>
        protected override bool RunDialog(IntPtr hWndOwner) {
            NativeMethods.WndProc hookProcPtr = new NativeMethods.WndProc(this.HookProc);
            NativeMethods.CHOOSEFONT cf = new NativeMethods.CHOOSEFONT();
            IntPtr screenDC = UnsafeNativeMethods.GetDC(NativeMethods.NullHandleRef);
            NativeMethods.LOGFONT lf = new NativeMethods.LOGFONT();
 
            Graphics graphics = Graphics.FromHdcInternal(screenDC);
 
            // SECREVIEW : The Font.ToLogFont method is marked with the 'unsafe' modifier cause it needs to write bytes into the logFont struct,
            //             here we are passing that struct so the assert is safe.
            //
            IntSecurity.ObjectFromWin32Handle.Assert();
            try {
                Font.ToLogFont(lf, graphics);
            }
            finally {
                CodeAccessPermission.RevertAssert();
                graphics.Dispose();
            }
            UnsafeNativeMethods.ReleaseDC(NativeMethods.NullHandleRef, new HandleRef(null, screenDC));
 
            IntPtr logFontPtr = IntPtr.Zero;
            try {
                logFontPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(NativeMethods.LOGFONT)));
                Marshal.StructureToPtr(lf, logFontPtr, false);
 
                cf.lStructSize = Marshal.SizeOf(typeof(NativeMethods.CHOOSEFONT));
                cf.hwndOwner = hWndOwner;
                cf.hDC = IntPtr.Zero;
                cf.lpLogFont = logFontPtr;
                cf.Flags = Options | NativeMethods.CF_INITTOLOGFONTSTRUCT | NativeMethods.CF_ENABLEHOOK;
                if (minSize > 0 || maxSize > 0) {
                    cf.Flags |= NativeMethods.CF_LIMITSIZE;
                }
 
                //if ShowColor=true then try to draw the sample text in color,
                //if ShowEffects=false then we will draw the sample text in standard control text color regardless.
                //(limitation of windows control)
                //
                if (ShowColor || ShowEffects) {
                    cf.rgbColors = ColorTranslator.ToWin32(color);
                }
                else {
                    cf.rgbColors = ColorTranslator.ToWin32(SystemColors.ControlText);
                }
 
                cf.lpfnHook = hookProcPtr;
                cf.hInstance = UnsafeNativeMethods.GetModuleHandle(null);
                cf.nSizeMin = minSize;
                if (maxSize == 0) {
                    cf.nSizeMax = Int32.MaxValue;
                }
                else {
                    cf.nSizeMax = maxSize;
                }
                Debug.Assert(cf.nSizeMin <= cf.nSizeMax, "min and max font sizes are the wrong way around");
                if (!SafeNativeMethods.ChooseFont(cf)) return false;
 
 
                NativeMethods.LOGFONT lfReturned = null;
                lfReturned = (NativeMethods.LOGFONT)UnsafeNativeMethods.PtrToStructure(logFontPtr, typeof(NativeMethods.LOGFONT));
 
                if (lfReturned.lfFaceName != null && lfReturned.lfFaceName.Length > 0) {
                    lf = lfReturned;
                    UpdateFont(lf);
                    UpdateColor(cf.rgbColors);
                }
 
                return true;
            }
            finally {
                if (logFontPtr != IntPtr.Zero)
                    Marshal.FreeCoTaskMem(logFontPtr);
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.SetOption"]/*' />
        /// <devdoc>
        ///     Sets the given option to the given boolean value.
        /// </devdoc>
        /// <internalonly/>
        internal void SetOption(int option, bool value) {
            if (value) {
                options |= option;
            }
            else {
                options &= ~option;
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ShouldSerializeFont"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Indicates whether the <see cref='System.Windows.Forms.FontDialog.Font'/> property should be
        ///       persisted.
        ///    </para>
        /// </devdoc>
        private bool ShouldSerializeFont() {
            return !Font.Equals(Control.DefaultFont);
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.ToString"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>
        ///       Retrieves a string that includes the name of the current font selected in
        ///       the dialog box.
        ///       
        ///    </para>
        /// </devdoc>
        public override string ToString() {
            string s = base.ToString();
            return s + ",  Font: " + Font.ToString();
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.UpdateColor"]/*' />
        /// <devdoc>
        /// </devdoc>
        /// <internalonly/>
        private void UpdateColor(int rgb) {
            if (ColorTranslator.ToWin32(color) != rgb) {
                color = ColorTranslator.FromOle(rgb);
                usingDefaultIndirectColor = false; 
            }
        }
 
        /// <include file='doc\FontDialog.uex' path='docs/doc[@for="FontDialog.UpdateFont"]/*' />
        /// <devdoc>
        /// </devdoc>
        /// <internalonly/>
        private void UpdateFont(NativeMethods.LOGFONT lf) {
            IntPtr screenDC = UnsafeNativeMethods.GetDC(NativeMethods.NullHandleRef);
            try {
                Font fontInWorldUnits = null;
                try {
                    IntSecurity.UnmanagedCode.Assert();
                    try {
                        fontInWorldUnits = Font.FromLogFont(lf, screenDC);
                    }
                    finally {
                        CodeAccessPermission.RevertAssert();
                    }
 
                    // The dialog claims its working in points (a device-independent unit),
                    // but actually gives us something in world units (device-dependent).
                    font = ControlPaint.FontInPoints(fontInWorldUnits);
                }
                finally {
                    if (fontInWorldUnits != null) {
                        fontInWorldUnits.Dispose();
                    }
                }
            }
            finally {
                UnsafeNativeMethods.ReleaseDC(NativeMethods.NullHandleRef, new HandleRef(null, screenDC));
            }
        }
 
    }
}