//------------------------------------------------------------------------------
// <copyright file="Clipboard.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

/*
 */
namespace System.Windows.Forms {
    
    using System;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    using System.Security;

    using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
    using System.Globalization;
    using System.Security.Permissions;
    using System.Collections;
    
    /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard"]/*' />
    /// <devdoc>
    ///    <para>Provides methods to place data on and retrieve data from the system clipboard. This class cannot be inherited.</para>
    /// </devdoc>
    public sealed class Clipboard {

        // not creatable...
        //
        private Clipboard() {
        }

        // 
        // Checks the validity of format while setting data into ClipBoard
        //
        private static bool IsFormatValid(DataObject data) {
            return IsFormatValid(data.GetFormats());
        }

        internal static bool IsFormatValid(string[] formats) {

            Debug.Assert(formats != null, "Null returned from GetFormats");
            if (formats != null) {
                if (formats.Length <= 4) {
                    for (int i = 0; i < formats.Length; i++) {
                        switch (formats[i]) {
                            case "Text":
                            case "UnicodeText":
                            case "System.String":
                            case "Csv":
                                break;
                            default:
                                return false;

                        }
                    }
                    return true;
                }
            }
            return false;
        }

        internal static bool IsFormatValid(FORMATETC[] formats) {

            Debug.Assert(formats != null, "Null returned from GetFormats");
            if (formats != null) {
                if (formats.Length <= 4) {
                    for (int i = 0; i < formats.Length; i++) {
                        short format = formats[i].cfFormat;
                        if (format != NativeMethods.CF_TEXT &&
                            format !=  NativeMethods.CF_UNICODETEXT && 
                            format !=  DataFormats.GetFormat("System.String").Id &&
                            format !=  DataFormats.GetFormat("Csv").Id) {
                                return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetDataObject"]/*' />
        /// <devdoc>
        /// <para>Places nonpersistent data on the system <see cref='System.Windows.Forms.Clipboard'/>.</para>
        /// </devdoc>
        public static void SetDataObject(object data) {
            SetDataObject(data, false);
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetDataObject2"]/*' />
        /// <devdoc>
        /// <para>Overload that uses default values for retryTimes and retryDelay.</para>
        /// </devdoc>
        public static void SetDataObject(object data, bool copy) {
            SetDataObject(data, copy, 10 /*retryTimes*/, 100 /*retryDelay*/);
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetDataObject1"]/*' />
        /// <devdoc>
        /// <para>Places data on the system <see cref='System.Windows.Forms.Clipboard'/> and uses copy to specify whether the data 
        ///    should remain on the <see cref='System.Windows.Forms.Clipboard'/>
        ///    after the application exits.</para>
        /// </devdoc>
        [UIPermission(SecurityAction.Demand, Clipboard=UIPermissionClipboard.OwnClipboard)]
        public static void SetDataObject(object data, bool copy, int retryTimes, int retryDelay) {
            if (Application.OleRequired() != System.Threading.ApartmentState.STA) {
                throw new System.Threading.ThreadStateException(SR.GetString(SR.ThreadMustBeSTA));
            }

            if (data == null) {
                throw new ArgumentNullException("data");
            }

            if (retryTimes < 0) {
                throw new ArgumentOutOfRangeException("retryTimes", SR.GetString(SR.InvalidLowBoundArgumentEx, "retryTimes", retryTimes.ToString(CultureInfo.CurrentCulture), (0).ToString(CultureInfo.CurrentCulture)));
            }

            if (retryDelay < 0) {
                throw new ArgumentOutOfRangeException("retryDelay", SR.GetString(SR.InvalidLowBoundArgumentEx, "retryDelay", retryDelay.ToString(CultureInfo.CurrentCulture), (0).ToString(CultureInfo.CurrentCulture)));
            }

            
            DataObject dataObject = null;
            if (!(data is IComDataObject)) {
                dataObject = new DataObject(data);
            }

            bool restrictedFormats = false;

            // If the caller doesnt have AllClipBoard permission then we allow only restricted formats.
            // These formats are TEXT, UNICODETEXT,String and CSV
            try 
            {
                IntSecurity.ClipboardRead.Demand();
            }
            catch (SecurityException)
            {
                // We dont have allClipBoard so we can set only data in the following formats
                // TEXT, UNICODETEXT, and CSV.
                restrictedFormats = true;
            }

            // Compute the format of the "data" passed in iff setText == true;
            
            if (restrictedFormats)
            {
                if (dataObject == null)
                {
                    dataObject = data as DataObject;
                }
                if (!IsFormatValid(dataObject))
                {
                    throw new SecurityException(SR.GetString(SR.ClipboardSecurityException));
                }
            }

            if (dataObject != null) {
                dataObject.RestrictedFormats = restrictedFormats;
            }
            int hr, retry = retryTimes;
            
            IntSecurity.UnmanagedCode.Assert();

            try
            {
                do {
                    if (data is IComDataObject) {
                        hr = UnsafeNativeMethods.OleSetClipboard((IComDataObject)data);
                    }
                    else {
                        hr = UnsafeNativeMethods.OleSetClipboard(dataObject);
                    }
                    if (hr != 0) {
                        if (retry == 0) {
                            ThrowIfFailed(hr);
                        }
                        retry--;
                        System.Threading.Thread.Sleep(retryDelay /*ms*/);
                    }
                }
                while (hr != 0);

                if (copy) {
                    retry = retryTimes;
                    do {
                        hr = UnsafeNativeMethods.OleFlushClipboard();
                        if (hr != 0) {
                            if (retry == 0) {
                                ThrowIfFailed(hr);
                            }
                            retry--;
                            System.Threading.Thread.Sleep(retryDelay /*ms*/);
                        }
                    }
                    while (hr != 0);
                }
            }
            finally
            {
                CodeAccessPermission.RevertAssert();   
            }
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetDataObject"]/*' />
        /// <devdoc>
        ///    <para>Retrieves the data that is currently on the system
        ///    <see cref='System.Windows.Forms.Clipboard'/>.</para>
        /// </devdoc>
        public static IDataObject GetDataObject() {
            Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "ClipboardRead Demanded");
            IntSecurity.ClipboardRead.Demand();

            if (Application.OleRequired() != System.Threading.ApartmentState.STA) {

                // only throw if a message loop was started. This makes the case of trying
                // to query the clipboard from your finalizer or non-ui MTA thread
                // silently fail, instead of making your app die.
                //
                // however, if you are trying to write a normal windows forms app and 
                // forget to set the STAThread attribute, we will correctly report
                // an error to aid in debugging.
                //
                if (Application.MessageLoop) {
                    throw new System.Threading.ThreadStateException(SR.GetString(SR.ThreadMustBeSTA));
                }
                else {
                    return null;
                }
            }
            // Vswhidbey :: 476911. We need to retry the GetDataObject() since the clipBaord is busy sometimes and hence the GetDataObject would fail with ClipBoardException.
            return GetDataObject(10 /*retryTimes*/, 100 /*retryDelay*/);
        }

        // Private method to help accessing clipBoard for know retries before failing.
        private static IDataObject GetDataObject(int retryTimes, int retryDelay)
        {
            IComDataObject dataObject = null;
            int hr, retry = retryTimes;
            do {
                hr = UnsafeNativeMethods.OleGetClipboard(ref dataObject);
                if (hr != 0) {
                    if (retry == 0) {
                        ThrowIfFailed(hr);
                    }
                    retry--;
                    System.Threading.Thread.Sleep(retryDelay /*ms*/);
                }
            }
            while (hr != 0);

            if (dataObject != null) {
                if (dataObject is IDataObject && !Marshal.IsComObject(dataObject)) {
                    return (IDataObject)dataObject;
                }
                else {
                    return new DataObject(dataObject);
                }
            }
            return null;
        }

        // <-- WHIDBEY ADDITIONS 

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.Clear"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void Clear() {
            Clipboard.SetDataObject(new DataObject());
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.ContainsAudio"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static bool ContainsAudio() {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetDataPresent(DataFormats.WaveAudio, false);
            }

            return false;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.ContainsData"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static bool ContainsData(string format) {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetDataPresent(format, false);
            }

            return false;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.ContainsFileDropList"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static bool ContainsFileDropList() {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetDataPresent(DataFormats.FileDrop, true);
            }

            return false;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.ContainsImage"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static bool ContainsImage() {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetDataPresent(DataFormats.Bitmap, true);
            }

            return false;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.ContainsText"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static bool ContainsText() {
            if (Environment.OSVersion.Platform != System.PlatformID.Win32NT ||
                Environment.OSVersion.Version.Major < 5) 
            {
                return ContainsText(TextDataFormat.Text);
            }
            else {
                return ContainsText(TextDataFormat.UnicodeText);
            }
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.ContainsText1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static bool ContainsText(TextDataFormat format) {
            // valid values are 0x0-0x4 inclusive
            if (!ClientUtils.IsEnumValid(format, (int)format, (int)TextDataFormat.Text, (int)TextDataFormat.CommaSeparatedValue)){
                throw new InvalidEnumArgumentException("format", (int)format, typeof(TextDataFormat));
            }

            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetDataPresent(ConvertToDataFormats(format), false);
            }

            return false;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetAudioStream"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static Stream GetAudioStream() {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetData(DataFormats.WaveAudio, false) as Stream;
            }

            return null;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetData"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static object GetData(string format) {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetData(format);
            }

            return null;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetFileDropList"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static StringCollection GetFileDropList() {
            IDataObject dataObject = Clipboard.GetDataObject();
            StringCollection retVal = new StringCollection();

            if (dataObject != null) {
                string[] strings = dataObject.GetData(DataFormats.FileDrop, true) as string[];
                if (strings != null) {
                    retVal.AddRange(strings);
                }
            }

            return retVal;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetImage"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static Image GetImage() {
            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                return dataObject.GetData(DataFormats.Bitmap, true) as Image;
            }

            return null;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetText"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string GetText() {
            // Pass in Text format for Win98...
            if (Environment.OSVersion.Platform != System.PlatformID.Win32NT ||
                Environment.OSVersion.Version.Major < 5) 
            {
                return GetText(TextDataFormat.Text);
            }
            else {
                return GetText(TextDataFormat.UnicodeText);
            }
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.GetText1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static string GetText(TextDataFormat format) {
            // valid values are 0x0 to 0x4 inclusive
            if (!ClientUtils.IsEnumValid(format, (int)format, (int)TextDataFormat.Text, (int)TextDataFormat.CommaSeparatedValue))
            {
                throw new InvalidEnumArgumentException("format", (int)format, typeof(TextDataFormat));
            }

            IDataObject dataObject = Clipboard.GetDataObject();
            if (dataObject != null) {
                string text = dataObject.GetData(ConvertToDataFormats(format), false) as string;
                if (text != null) {
                    return text;
                }
            }

            return String.Empty;
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetAudio"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetAudio(byte[] audioBytes) {
            if (audioBytes == null) {
                throw new ArgumentNullException("audioBytes");
            }
            SetAudio(new MemoryStream(audioBytes));
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetAudio1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetAudio(Stream audioStream) {
            if (audioStream == null) {
                throw new ArgumentNullException("audioStream");
            }
            IDataObject dataObject = new DataObject();
            dataObject.SetData(DataFormats.WaveAudio, false, audioStream);
            Clipboard.SetDataObject(dataObject, true);
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetData"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetData(string format, object data) {
            //Note: We delegate argument checking to IDataObject.SetData, if it wants to do so.
            IDataObject dataObject = new DataObject();
            dataObject.SetData(format, data);
            Clipboard.SetDataObject(dataObject, true);
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetFileDropList"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetFileDropList(StringCollection filePaths) {
            if (filePaths == null) {
                throw new ArgumentNullException("filePaths");
            }
            // VsWhidbey:432618 throw Argument exception for zero-length filepath collection.
            if (filePaths.Count == 0)
            {
                throw new ArgumentException(SR.GetString(SR.CollectionEmptyException));
            }

            //VSWhidbey #163538 - Validate the paths to make sure they don't contain invalid characters
            foreach (string path in filePaths) {
                try {
                    string temp = Path.GetFullPath(path);
                }
                catch (Exception e) {
                    if (ClientUtils.IsSecurityOrCriticalException(e)) {
                        throw;
                    }

                    throw new ArgumentException(SR.GetString(SR.Clipboard_InvalidPath, path, "filePaths"), e);
                }
            }

            if (filePaths.Count > 0) {
                IDataObject dataObject = new DataObject();
                string[] strings = new string[filePaths.Count];
                filePaths.CopyTo(strings, 0);
                dataObject.SetData(DataFormats.FileDrop, true, strings);
                Clipboard.SetDataObject(dataObject, true);
            }
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetImage"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetImage(Image image) {
            if (image == null) {
                throw new ArgumentNullException("image");
            }
            IDataObject dataObject = new DataObject();
            dataObject.SetData(DataFormats.Bitmap, true, image);
            Clipboard.SetDataObject(dataObject, true);
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetText"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetText(string text) {
            // Pass in Text format for Win98...
            if (Environment.OSVersion.Platform != System.PlatformID.Win32NT ||
                Environment.OSVersion.Version.Major < 5) 
            {
                SetText(text, TextDataFormat.Text);
            }
            else {
               SetText(text, TextDataFormat.UnicodeText);
            }
        }

        /// <include file='doc\Clipboard.uex' path='docs/doc[@for="Clipboard.SetText1"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void SetText(string text, TextDataFormat format) {
            if (String.IsNullOrEmpty(text)) {
                throw new ArgumentNullException("text");
            }

            //valid values are 0x0 to 0x4
            if (!ClientUtils.IsEnumValid(format, (int)format, (int)TextDataFormat.Text, (int)TextDataFormat.CommaSeparatedValue))
            {
                throw new InvalidEnumArgumentException("format", (int)format, typeof(TextDataFormat));
            }

            IDataObject dataObject = new DataObject();
            dataObject.SetData(ConvertToDataFormats(format), false, text);
            Clipboard.SetDataObject(dataObject, true);
        }

        private static string ConvertToDataFormats(TextDataFormat format) {
            switch (format) {
            case TextDataFormat.Text: 
                return DataFormats.Text;
                
            case TextDataFormat.UnicodeText: 
                return DataFormats.UnicodeText;
            
            case TextDataFormat.Rtf: 
                return DataFormats.Rtf;
                
            case TextDataFormat.Html: 
                return DataFormats.Html;
                
            case TextDataFormat.CommaSeparatedValue: 
                return DataFormats.CommaSeparatedValue;
            }

            return DataFormats.UnicodeText;
        }

        // END - WHIDBEY ADDITIONS -->

        private static void ThrowIfFailed(int hr) {
            // 
            if (hr != 0) {
                ExternalException e = new ExternalException(SR.GetString(SR.ClipboardOperationFailed), hr);
                throw e;
            }
        }
    }
}
