// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;
using mshtml;
using OpenLiveWriter.Controls;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Diagnostics;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Extensibility.ImageEditing;
using OpenLiveWriter.HtmlParser.Parser;
using OpenLiveWriter.Localization;
using OpenLiveWriter.PostEditor.ContentSources;
using OpenLiveWriter.PostEditor.Emoticons;
using OpenLiveWriter.PostEditor.PostHtmlEditing.ImageEditing.Decorators;

namespace OpenLiveWriter.PostEditor.PostHtmlEditing
{
    public static class ImageInsertionManager
    {
        private static Uri LoadingImagePath = new Uri(Path.Combine(ApplicationEnvironment.InstallationDirectory, @"html\loading.png"));
        private static bool selectionChanged;

        /// <summary>
        /// Scans the DOM for images that have not yet been properly initialized by the editor.
        /// As part of the initialization, the default image settings and effects will be applied to the image. 
        /// </summary>
        internal static void ScanAndInitializeNewImages(IBlogPostHtmlEditor currentEditor, ISupportingFileService fileService, IEditorAccount editorAccount, ContentEditor editor, Control owner, bool useDefaultTargetSettings, bool selectLastImage)
        {
            if (currentEditor is BlogPostHtmlEditorControl)
            {
                // Scanning the images and doing basic initialization is done on the UI thread.
                List<NewImageInfo> newImages = ScanImages(currentEditor, editorAccount, editor, useDefaultTargetSettings);

                if (newImages.Count > 0)
                {
                    // The time-consuming initialization is done in a background thread.
                    ImageInitializationAsyncOperation imageInitializer = new ImageInitializationAsyncOperation(newImages, fileService, owner);
                    imageInitializer.Completed += new EventHandler((sender, e) => 
                        ProcessInitializedImages(newImages, currentEditor, editor, owner, selectLastImage));

                    editor.DisposeOnEditorChange(imageInitializer);

                    imageInitializer.Start();
                }
            }
        }

        private static List<NewImageInfo> ScanImages(IBlogPostHtmlEditor currentEditor, IEditorAccount editorAccount, ContentEditor editor, bool useDefaultTargetSettings)
        {
            List<NewImageInfo> newImages = new List<NewImageInfo>();

            ApplicationPerformance.ClearEvent("InsertImage");
            ApplicationPerformance.StartEvent("InsertImage");

            using (new WaitCursor())
            {
                IHTMLElement2 postBodyElement = (IHTMLElement2)((BlogPostHtmlEditorControl)currentEditor).PostBodyElement;
                if (postBodyElement != null)
                {
                    foreach (IHTMLElement imgElement in postBodyElement.getElementsByTagName("img"))
                    {
                        string imageSrc = imgElement.getAttribute("srcDelay", 2) as string;

                        if (string.IsNullOrEmpty(imageSrc))
                        {
                            imageSrc = imgElement.getAttribute("src", 2) as string;
                        }

                        // WinLive 96840 - Copying and pasting images within shared canvas should persist source
                        // decorator settings. "wlCopySrcUrl" is inserted while copy/pasting within canvas.
                        bool copyDecoratorSettings = false;
                        string attributeCopySrcUrl = imgElement.getAttribute("wlCopySrcUrl", 2) as string;
                        if (!string.IsNullOrEmpty(attributeCopySrcUrl))
                        {
                            copyDecoratorSettings = true;
                            imgElement.removeAttribute("wlCopySrcUrl", 0);
                        }

                        // Check if we need to apply default values for image decorators
                        bool applyDefaultDecorator = true;
                        string attributeNoDefaultDecorator = imgElement.getAttribute("wlNoDefaultDecorator", 2) as string;
                        if (!string.IsNullOrEmpty(attributeNoDefaultDecorator) && string.Compare(attributeNoDefaultDecorator, "TRUE", StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            applyDefaultDecorator = false;
                            imgElement.removeAttribute("wlNoDefaultDecorator", 0);
                        }

                        string applyDefaultMargins = imgElement.getAttribute("wlApplyDefaultMargins", 2) as string;
                        if (!String.IsNullOrEmpty(applyDefaultMargins))
                        {
                            DefaultImageSettings defaultImageSettings = new DefaultImageSettings(editorAccount.Id, editor.DecoratorsManager);
                            MarginStyle defaultMargin = defaultImageSettings.GetDefaultImageMargin();
                            // Now apply it to the image
                            imgElement.style.marginTop = String.Format(CultureInfo.InvariantCulture, "{0} px", defaultMargin.Top);
                            imgElement.style.marginLeft = String.Format(CultureInfo.InvariantCulture, "{0} px", defaultMargin.Left);
                            imgElement.style.marginBottom = String.Format(CultureInfo.InvariantCulture, "{0} px", defaultMargin.Bottom);
                            imgElement.style.marginRight = String.Format(CultureInfo.InvariantCulture, "{0} px", defaultMargin.Right);
                            imgElement.removeAttribute("wlApplyDefaultMargins", 0);
                        }

                        if ((UrlHelper.IsFileUrl(imageSrc) || IsFullPath(imageSrc)) && !ContentSourceManager.IsSmartContent(imgElement))
                        {
                            Uri imageSrcUri = new Uri(imageSrc);
                            try
                            {
                                BlogPostImageData imageData = BlogPostImageDataList.LookupImageDataByInlineUri(editor.ImageList, imageSrcUri);
                                Emoticon emoticon = EmoticonsManager.GetEmoticon(imgElement);
                                if(imageData == null && emoticon != null)
                                {
                                    // This is usually an emoticon copy/paste and needs to be cleaned up.
                                    Uri inlineImageUri = editor.EmoticonsManager.GetInlineImageUri(emoticon);
                                    imgElement.setAttribute("src", UrlHelper.SafeToAbsoluteUri(inlineImageUri), 0);
                                }
                                else if (imageData == null)
                                {
                                    if (!File.Exists(imageSrcUri.LocalPath))
                                        throw new FileNotFoundException(imageSrcUri.LocalPath);

                                    // WinLive 188841: Manually attach the behavior so that the image cannot be selected or resized while its loading.
                                    DisabledImageElementBehavior disabledImageBehavior = new DisabledImageElementBehavior(editor.IHtmlEditorComponentContext);
                                    disabledImageBehavior.AttachToElement(imgElement);

                                    Size sourceImageSize = ImageUtils.GetImageSize(imageSrcUri.LocalPath);
                                    ImagePropertiesInfo imageInfo = new ImagePropertiesInfo(imageSrcUri, sourceImageSize, new ImageDecoratorsList(editor.DecoratorsManager, new BlogPostSettingsBag()));
                                    DefaultImageSettings defaultImageSettings = new DefaultImageSettings(editorAccount.Id, editor.DecoratorsManager);
                                    
                                    // Make sure this is set because some imageInfo properties depend on it.
                                    imageInfo.ImgElement = imgElement;

                                    bool isMetafile = ImageHelper2.IsMetafile(imageSrcUri.LocalPath);
                                    ImageClassification imgClass = ImageHelper2.Classify(imageSrcUri.LocalPath);
                                    if (!isMetafile && ((imgClass & ImageClassification.AnimatedGif) != ImageClassification.AnimatedGif))
                                    {
                                        // WinLive 96840 - Copying and pasting images within shared canvas should persist source
                                        // decorator settings.
                                        if (copyDecoratorSettings)
                                        {
                                            // Try to look up the original copied source image. 
                                            BlogPostImageData imageDataOriginal = BlogPostImageDataList.LookupImageDataByInlineUri(editor.ImageList, new Uri(attributeCopySrcUrl));
                                            if (imageDataOriginal != null && imageDataOriginal.GetImageSourceFile() != null)
                                            {
                                                // We have the original image reference, so lets make a clone of it.
                                                BlogPostSettingsBag originalBag = (BlogPostSettingsBag)imageDataOriginal.ImageDecoratorSettings.Clone();
                                                ImageDecoratorsList originalDecoratorsList = new ImageDecoratorsList(editor.DecoratorsManager, originalBag);

                                                ImageFileData originalImageFileData = imageDataOriginal.GetImageSourceFile();
                                                Size originalImageSize = new Size(originalImageFileData.Width, originalImageFileData.Height);
                                                imageInfo = new ImagePropertiesInfo(originalImageFileData.Uri, originalImageSize, originalDecoratorsList);
                                            }
                                            else
                                            {
                                                // There are probably decorators applied to the image, but in a different editor so we can't access them. 
                                                // We probably don't want to apply any decorators to this image, so apply blank decorators and load the
                                                // image as full size so it looks like it did before.
                                                imageInfo.ImageDecorators = defaultImageSettings.LoadBlankLocalImageDecoratorsList();
                                                imageInfo.InlineImageSizeName = ImageSizeName.Full;
                                            }
                                        }
                                        else if (applyDefaultDecorator)
                                        {
                                            imageInfo.ImageDecorators = defaultImageSettings.LoadDefaultImageDecoratorsList();

                                            if ((imgClass & ImageClassification.TransparentGif) == ImageClassification.TransparentGif)
                                                imageInfo.ImageDecorators.AddDecorator(NoBorderDecorator.Id);
                                        }
                                        else
                                        {
                                            // Don't use default values for decorators
                                            imageInfo.ImageDecorators = defaultImageSettings.LoadBlankLocalImageDecoratorsList();
                                            imageInfo.InlineImageSizeName = ImageSizeName.Full;
                                        }
                                    }
                                    else
                                    {
                                        ImageDecoratorsList decorators = new ImageDecoratorsList(editor.DecoratorsManager, new BlogPostSettingsBag());
                                        decorators.AddDecorator(editor.DecoratorsManager.GetDefaultRemoteImageDecorators());
                                        imageInfo.ImageDecorators = decorators;
                                    }

                                    imageInfo.ImgElement = imgElement;
                                    imageInfo.DhtmlImageViewer = editorAccount.EditorOptions.DhtmlImageViewer;

                                    //discover the "natural" target settings from the DOM
                                    string linkTargetUrl = imageInfo.LinkTargetUrl;
                                    if (linkTargetUrl == imageSrc)
                                    {
                                        imageInfo.LinkTarget = LinkTargetType.IMAGE;
                                    }
                                    else if (!String.IsNullOrEmpty(linkTargetUrl) && !UrlHelper.IsFileUrl(linkTargetUrl))
                                    {
                                        imageInfo.LinkTarget = LinkTargetType.URL;
                                    }
                                    else
                                    {
                                        imageInfo.LinkTarget = LinkTargetType.NONE;
                                    }

                                    if (useDefaultTargetSettings)
                                    {
                                        if (!GlobalEditorOptions.SupportsFeature(ContentEditorFeature.SupportsImageClickThroughs) && imageInfo.DefaultLinkTarget == LinkTargetType.IMAGE)
                                            imageInfo.DefaultLinkTarget = LinkTargetType.NONE;

                                        if (imageInfo.LinkTarget == LinkTargetType.NONE)
                                            imageInfo.LinkTarget = imageInfo.DefaultLinkTarget;
                                        if (imageInfo.DefaultLinkOptions.ShowInNewWindow)
                                            imageInfo.LinkOptions.ShowInNewWindow = true;
                                        imageInfo.LinkOptions.UseImageViewer = imageInfo.DefaultLinkOptions.UseImageViewer;
                                        imageInfo.LinkOptions.ImageViewerGroupName = imageInfo.DefaultLinkOptions.ImageViewerGroupName;
                                    }

                                    Size defaultImageSize = defaultImageSettings.GetDefaultInlineImageSize();
                                    Size initialSize = ImageUtils.GetScaledImageSize(defaultImageSize.Width, defaultImageSize.Height, sourceImageSize);

                                    // add to list of new images
                                    newImages.Add(new NewImageInfo(imageInfo, imgElement, initialSize, disabledImageBehavior));
                                }
                                else
                                {
                                    // When switching blogs, try to adapt image viewer settings according to the blog settings.

                                    ImagePropertiesInfo imageInfo = new ImagePropertiesInfo(imageSrcUri, ImageUtils.GetImageSize(imageSrcUri.LocalPath), new ImageDecoratorsList(editor.DecoratorsManager, imageData.ImageDecoratorSettings));
                                    imageInfo.ImgElement = imgElement;
                                    // Make sure the new crop and tilt decorators get loaded
                                    imageInfo.ImageDecorators.MergeDecorators(DefaultImageSettings.GetImplicitLocalImageDecorators());
                                    string viewer = imageInfo.DhtmlImageViewer;
                                    if (viewer != editorAccount.EditorOptions.DhtmlImageViewer)
                                    {
                                        imageInfo.DhtmlImageViewer = editorAccount.EditorOptions.DhtmlImageViewer;
                                        imageInfo.LinkOptions = imageInfo.DefaultLinkOptions;
                                    }

                                    // If the image is an emoticon, update the EmoticonsManager with the image's uri so that duplicate emoticons can point to the same file.
                                    if (emoticon != null)
                                        editor.EmoticonsManager.SetInlineImageUri(emoticon, imageData.InlineImageFile.Uri);
                                }
                            }                            
                            catch (ArgumentException e)
                            {
                                Trace.WriteLine("Could not initialize image: " + imageSrc);
                                Trace.WriteLine(e.ToString());
                            }
                            catch (DirectoryNotFoundException)
                            {
                                Debug.WriteLine("Image file does not exist: " + imageSrc);
                            }
                            catch (FileNotFoundException)
                            {
                                Debug.WriteLine("Image file does not exist: " + imageSrc);
                            }
                            catch (IOException e)
                            {
                                Debug.WriteLine("Image file cannot be read: " + imageSrc + " " + e);                                
                                DisplayMessage.Show(MessageId.FileInUse, imageSrc);                                
                            }
                        }
                    }
                }
            }

            return newImages;
        }

        private static void ProcessInitializedImages(List<NewImageInfo> newImages, IBlogPostHtmlEditor currentEditor, ContentEditor editor, Control owner, bool selectLastImage)
        {
            // Remove all invalid images first
            List<NewImageInfo> newImagesToUpdate = new List<NewImageInfo>();
            for (int i = 0; i < newImages.Count; i++)
            {
                NewImageInfo info = newImages[i];

                if (info.Remove)
                {
                    using (ContentEditor.EditorUndoUnit undo = new ContentEditor.EditorUndoUnit(currentEditor, true))
                    {
                        info.DisabledImageBehavior.DetachFromElement();
                        HTMLElementHelper.RemoveElement(info.Element);
                        undo.Commit();
                    }
                }
                else
                {
                    newImagesToUpdate.Add(info);
                }
            }

            // Queue up processing for any remaining valid images
            MultiTaskHelper tasks = new MultiTaskHelper(30);
            for (int i = 0; i < newImagesToUpdate.Count; i++)
            {
                bool lastElement = i == (newImagesToUpdate.Count - 1);
                NewImageInfo info = newImagesToUpdate[i];

                tasks.AddWork(delegate
                {
                    // WinLive 214012: If the original insertion operation is undone before we get to this point make 
                    // sure we don't attempt to update the HTML. Elements that are removed via an undo, but that we 
                    // still hold a reference to, are put into another document whose readyState is "uninitialized".
                    // It's also possible that the picture has already been updated if a user undoes and redoes the 
                    // initial insertion multiple times.
                    IHTMLDocument2 doc = (IHTMLDocument2)info.Element.document;
                    if (doc == null || doc.readyState.Equals("uninitialized", StringComparison.OrdinalIgnoreCase) || 
                        BlogPostImageDataList.LookupImageDataByInlineUri(editor.ImageList, info.ImageData.InlineImageFile.Uri) != null)
                    {
                        info.DisabledImageBehavior.DetachFromElement();
                        return;
                    }

                    using (new QuickTimer("ProcessInitializedImage"))
                    using (ContentEditor.EditorUndoUnit undo = new ContentEditor.EditorUndoUnit(currentEditor, true))
                    {
                        editor.ImageList.AddImage(info.ImageData);

                        info.Element.setAttribute("src", UrlHelper.SafeToAbsoluteUri(info.ImageData.InlineImageFile.Uri), 0);
                        info.Element.removeAttribute("srcDelay", 0);
                        // Create the decorators
                        ImageEditingPropertyHandler.UpdateImageSource(info.ImageInfo, info.Element, editor, new ImageInsertHandler(), ImageDecoratorInvocationSource.InitialInsert);

                        // Manually detach the behavior so that the image can be selected and resized.
                        info.DisabledImageBehavior.DetachFromElement();

                        if (lastElement)
                        {
                            ApplicationPerformance.EndEvent("InsertImage");
                            HandleNewImage((BlogPostHtmlEditorControl)currentEditor, owner, info.Element, selectLastImage);
                        }

                        undo.Commit();

                        if (editor.ETWProvider != null)
                            editor.ETWProvider.WriteEvent("InlinePhotoEnd");
                    }
                });
            }

            // If there were no images to update, stop the perf timer
            if (newImagesToUpdate.Count == 0)
            {
                ApplicationPerformance.EndEvent("InsertImage");
            }

            // When the editor is closing, or changing to a new blog post we need to get rid of this object
            // which will then stop all the unfinished images from continuing to load.  
            editor.DisposeOnEditorChange(tasks);
            tasks.Start();
        }

        private static bool IsFullPath(string str)
        {
            try
            {
                return File.Exists(str);
            }
            catch (Exception e)
            {
                Trace.Fail("Surprising, File.Exists threw an exception: " + e);
                return false;
            }
        }

        public const int DELAYED_IMAGE_THRESHOLD = 2;

        /// <summary>
        /// Inserts a set of images into the current editor, optionally assigning each image an id.
        /// </summary>
        /// <param name="imagePaths">paths of the images to insert</param>
        internal static void InsertImagesCore(IBlogPostHtmlEditor currentEditor, ISupportingFileService fileService, IEditorAccount editorAccount, OpenLiveWriter.PostEditor.ContentEditor editor, string[] imagePaths)
        {
            using (OpenLiveWriter.PostEditor.ContentEditor.EditorUndoUnit undo = new OpenLiveWriter.PostEditor.ContentEditor.EditorUndoUnit(currentEditor))
            {                
                StringBuilder htmlBuilder = new StringBuilder();

                //calculate the size for inserted images (based on the user's saved default for this blog)
                DefaultImageSettings defaultImageSettings = new DefaultImageSettings(editorAccount.Id, editor.DecoratorsManager);
                Size defaultImageSize = defaultImageSettings.GetDefaultInlineImageSize();

                //Generate the default img HTML for the image paths.  Initially, the images are linked to the source image
                //path so that the DOM version of the HTML can be loaded.  Once the images are loaded into the DOM, we can apply
                //the image decorators to generate scaled images, borders, etc.
                ImagePropertiesInfo[] imageInfos = new ImagePropertiesInfo[imagePaths.Length];

                // don't insert into the title
                currentEditor.FocusBody();

                for (int i = 0; i < imageInfos.Length; i++)
                {
                    string imagePath = imagePaths[i];

                    Uri imgUri;
                    if (UrlHelper.IsUrl(imagePath))
                        imgUri = new Uri(imagePath);
                    else
                        imgUri = new Uri(UrlHelper.CreateUrlFromPath(imagePath));

                    Size imageSize = new Size(1,1);
                    if (imgUri.IsFile && File.Exists(imagePath))
                    {
                        if (!File.Exists(imgUri.LocalPath))
                            Trace.Fail("Error inserting image - the image URL was corrupted: " + imagePath);
                        else
                        {
                            try
                            {
                                //check the validity of the image file
                                imageSize = ImageUtils.GetImageSize(imagePath);
                            }
                            catch (Exception)
                            {
                                Trace.WriteLine("There is a problem with the image file: " + imagePath);

                                // Insert anyway, MSHTML will show a red X in place of the image.
                                htmlBuilder.AppendFormat(CultureInfo.InvariantCulture, "<img src=\"{0}\" />", HtmlUtils.EscapeEntities(UrlHelper.SafeToAbsoluteUri(imgUri)));

                                continue;
                            }
                        }
                    }

                    // If the image has an embedded thumbnail, we'll use it as a place holder for the <img src="..."> 
                    // until we generate an inline image and apply decorators.
                    Stream embeddedThumbnailStream = ImageHelper2.GetEmbeddedThumbnailStream(imagePath);

                    if(embeddedThumbnailStream != null)
                    {
                        // Save thumbnail to disk.
                        ISupportingFile imageFileEmbeddedThumbnail = fileService.CreateSupportingFile(Path.GetFileName(imagePath), Guid.NewGuid().ToString(), embeddedThumbnailStream);

                        imageSize = ImageUtils.GetScaledImageSize(defaultImageSize.Width, defaultImageSize.Height, imageSize);
                        //insert the default image html
                        String imageElementAttrs = String.Format(CultureInfo.InvariantCulture, " width=\"{0}\" height=\"{1}\"", imageSize.Width, imageSize.Height);

                        htmlBuilder.AppendFormat(CultureInfo.InvariantCulture, "<img src=\"{0}\" srcDelay=\"{1}\" {2} />", HtmlUtils.EscapeEntities(UrlHelper.SafeToAbsoluteUri(imageFileEmbeddedThumbnail.FileUri)), HtmlUtils.EscapeEntities(imgUri.ToString()), imageElementAttrs);
                    }
                    else if (currentEditor is BlogPostHtmlEditorControl && (imagePaths.Length > DELAYED_IMAGE_THRESHOLD || imageSize.Width * imageSize.Height > 16777216/*4096 X 4096*/))
                    {
                        // If we are using a delayed loading tactic then we insert using the srcdelay
                        htmlBuilder.Append(MakeHtmlForImageSourceDelay(imgUri.ToString()));
                    }
                    else
                    {
                        imageSize = ImageUtils.GetScaledImageSize(defaultImageSize.Width, defaultImageSize.Height, imageSize);
                        //insert the default image html
                        String imageElementAttrs = imgUri.IsFile ? String.Format(CultureInfo.InvariantCulture, " width=\"{0}\" height=\"{1}\"", imageSize.Width, imageSize.Height) : String.Empty;

                        htmlBuilder.AppendFormat(CultureInfo.InvariantCulture, "<img src=\"{0}\" {1} />", HtmlUtils.EscapeEntities(UrlHelper.SafeToAbsoluteUri(imgUri)), imageElementAttrs);
                    }
                }

                //insert the HTML into the editor
                currentEditor.InsertHtml(htmlBuilder.ToString(), false);

                selectionChanged = false;
                BlogPostHtmlEditorControl blogPostEditor = currentEditor as BlogPostHtmlEditorControl;
                if (blogPostEditor != null)
                    blogPostEditor.SelectionChanged += blogPostEditor_SelectionChanged;

                //now that the image HTML is inserted into the editor, apply the default settings to the new images.
                undo.Commit();
            }
        }
        
        private static void HandleNewImage(BlogPostHtmlEditorControl currentEditor, Control owner, IHTMLElement imageElement, bool select)
        {
            // If the selection has changed since we first inserted the img tag, don't force a new selection.
            currentEditor.SelectionChanged -= blogPostEditor_SelectionChanged;
            if (selectionChanged || !select)
                return;

            try
            {
                currentEditor.EmptySelection();
                // programmatically select the image
                currentEditor.SelectImage(imageElement);
                currentEditor.Focus();                                    
            }
            catch (Exception ex)
            {
                Trace.Fail("Unexpected error while attempting to select newly inserted image: " + ex.ToString());
            }
        }

        public static string MakeHtmlForImageSourceDelay(string path)
        {
            return
                String.Format(CultureInfo.InvariantCulture, "<img src=\"{1}\" srcDelay=\"{0}\" />", HtmlUtils.EscapeEntities(path), HtmlUtils.EscapeEntities(UrlHelper.SafeToAbsoluteUri(LoadingImagePath)));
        }

        internal static string ReferenceFixer(BeginTag tag, string reference, ISupportingFileService _fileService, OpenLiveWriter.PostEditor.ContentEditor editor)
        {
            // If it isnt a file url, then it wont be an image from supporting files.
            if (!UrlHelper.IsFile(reference))
                return reference;

            Uri uri = new Uri(reference);
            BlogPostImageData imageData = BlogPostImageDataList.LookupImageDataByInlineUri(editor.ImageList, uri);

            if (imageData == null)
            {
                if(BlogPostImageDataList.LookupImageDataByLinkedUri(editor.ImageList, uri) != null)
                    return null;

                return reference;
            }

            return imageData.ImageSourceFile.Uri.AbsoluteUri;
        }

        private static void blogPostEditor_SelectionChanged(object sender, EventArgs e)
        {
            selectionChanged = true;
        }
    }
}
