Back out solution for Unencrypted Preview as solution with WebBrowser is still incomplete Test Release 1.0.90
authorThomas
Fri, 09 Mar 2018 15:32:48 +0100
branchTest Release 1.0.90
changeset 2050bc0d87b9d346
parent 2049 9730136a2183
child 2077 4864cc3beb6d
Back out solution for Unencrypted Preview as solution with WebBrowser is still incomplete
UI/FormControlPreviewMessage.xaml
UI/FormControlPreviewMessage.xaml.cs
     1.1 --- a/UI/FormControlPreviewMessage.xaml	Fri Mar 09 10:52:54 2018 +0100
     1.2 +++ b/UI/FormControlPreviewMessage.xaml	Fri Mar 09 15:32:48 2018 +0100
     1.3 @@ -408,12 +408,22 @@
     1.4              </Separator>
     1.5          </Grid>
     1.6  
     1.7 -        <WebBrowser x:Name="WebBrowser"
     1.8 -                    Grid.Row="6"
     1.9 -                    HorizontalAlignment="Stretch"
    1.10 -                    VerticalAlignment="Stretch"
    1.11 -                    Margin="0,10,0,0"
    1.12 -                    Visibility="{Binding Path='IsNoteModeEnabled', Mode=OneWay, Converter={StaticResource InvertBoolToVisibility}}" />
    1.13 +        <!-- Message body -->
    1.14 +        <WindowsFormsHost x:Name="WindowsFormsHostMessageBody"
    1.15 +                          Grid.Row="6"
    1.16 +                          HorizontalAlignment="Stretch"
    1.17 +                          VerticalAlignment="Stretch"
    1.18 +                          Margin="0,10,0,0"
    1.19 +                          Visibility="{Binding Path='IsNoteModeEnabled', Mode=OneWay, Converter={StaticResource InvertBoolToVisibility}}">
    1.20 +            <wf:RichTextBox x:Name="RichTextBoxMessageBody"
    1.21 +                            BorderStyle="None"
    1.22 +                            ReadOnly="True"
    1.23 +                            LinkClicked="RichTextBoxMessageBody_LinkClicked" 
    1.24 +                            MouseClick="RichTextBoxMessageBody_HideCaret"
    1.25 +                            Click="RichTextBoxMessageBody_HideCaret"
    1.26 +                            MouseDoubleClick="RichTextBoxMessageBody_HideCaret"
    1.27 +                            GotFocus="RichTextBoxMessageBody_HideCaret"/>
    1.28 +        </WindowsFormsHost>
    1.29  
    1.30      </Grid>
    1.31  </UserControl>
    1.32 \ No newline at end of file
     2.1 --- a/UI/FormControlPreviewMessage.xaml.cs	Fri Mar 09 10:52:54 2018 +0100
     2.2 +++ b/UI/FormControlPreviewMessage.xaml.cs	Fri Mar 09 15:32:48 2018 +0100
     2.3 @@ -1,8 +1,17 @@
     2.4  ´╗┐using System;
     2.5 +using System.Collections.Generic;
     2.6  using System.ComponentModel;
     2.7 +using System.Drawing;
     2.8 +using System.Drawing.Imaging;
     2.9 +using System.IO;
    2.10 +using System.Runtime.Remoting.Metadata.W3cXsd2001;
    2.11 +using System.Text;
    2.12  using System.Text.RegularExpressions;
    2.13  using System.Windows;
    2.14  using System.Windows.Controls;
    2.15 +using System.Linq;
    2.16 +using System.Windows.Media.Imaging;
    2.17 +using Outlook = Microsoft.Office.Interop.Outlook;
    2.18  
    2.19  namespace pEp.UI
    2.20  {
    2.21 @@ -35,6 +44,27 @@
    2.22  
    2.23          private State displayState;
    2.24  
    2.25 +        private const string RtfPictureTag = "pichgoal";
    2.26 +        private const string RtfPicturePlaceHolder = "__RTF_PICTURE_PLACE_HOLDER__";
    2.27 +
    2.28 +        // Mapping mode used for the GdipEmfToWmfBits() method. See: https://msdn.microsoft.com/en-us/library/windows/desktop/dd162980(v=vs.85).aspx
    2.29 +        private static int MM_ANISOTROPIC = 8;
    2.30 +
    2.31 +        // Temporary folder name for temporary images files         
    2.32 +        private string _TempFolder;
    2.33 +        private string TempFolder
    2.34 +        {
    2.35 +            get
    2.36 +            {
    2.37 +                if (string.IsNullOrEmpty(_TempFolder))
    2.38 +                {
    2.39 +                    _TempFolder = GetTempFileFolder();
    2.40 +                }
    2.41 +
    2.42 +                return _TempFolder;
    2.43 +            }
    2.44 +        }
    2.45 +
    2.46          /**************************************************************
    2.47           * 
    2.48           * Constructors
    2.49 @@ -52,7 +82,7 @@
    2.50              this.displayState.PropertyChanged += DisplayState_PropertyChanged;
    2.51              this.DataContext = this.displayState;
    2.52  
    2.53 -            // Connect event handler to dispose of all controls and fields if form is not visible
    2.54 +            // connecting event handler where all controls and fields disposed in case this.IsVisible == false
    2.55              this.IsVisibleChanged += DisposeOfAllComponents;
    2.56          }
    2.57  
    2.58 @@ -89,115 +119,555 @@
    2.59           *************************************************************/
    2.60  
    2.61          /// <summary>
    2.62 -        /// Event handler to dispose of all controls and fields if form is not visible
    2.63 +        // Handler where controls, fields and temporary files are disposed in case this.IsVisible == false
    2.64          /// </summary>
    2.65          private void DisposeOfAllComponents(object sender, DependencyPropertyChangedEventArgs e)
    2.66          {
    2.67 -            if (this.IsVisible == false)
    2.68 +            if (!this.IsVisible)
    2.69              {
    2.70 -                this.WebBrowser.Dispose();
    2.71 -                this.WebBrowser = null;
    2.72 +                this.WindowsFormsHostMessageBody.Dispose();
    2.73 +                this.RichTextBoxMessageBody.Dispose();
    2.74                  this.ButtonForward = null;
    2.75                  this.ButtonReply = null;
    2.76                  this.ButtonReplyAll = null;
    2.77                  this.displayState = null;
    2.78                  this.displayState = null;
    2.79                  this.GridLayoutRoot = null;
    2.80 +
    2.81 +                // Delete temp directory for temporary images files
    2.82 +                if (!string.IsNullOrEmpty(_TempFolder))
    2.83 +                {
    2.84 +                    try
    2.85 +                    {
    2.86 +                        Directory.Delete(_TempFolder, true);
    2.87 +                    }
    2.88 +                    catch (Exception ex)
    2.89 +                    {
    2.90 +                        Log.Error("FormControlPreviewMessage.xaml.cs DisposeOfAllComponents: Could not delete temp directory. " + ex.Message);
    2.91 +                    }
    2.92 +                }
    2.93              }
    2.94          }
    2.95  
    2.96          /// <summary>
    2.97 -        /// Sets the web browser content in the UI from the display state message.
    2.98 +        /// Sets the rich text box content in the UI from the display state message.
    2.99          /// </summary>
   2.100 -        private void SetWebBrowserContent()
   2.101 +        private void SetRichTextBoxContent()
   2.102          {
   2.103 -            string displayString = null;
   2.104 +            bool success = false;
   2.105  
   2.106 -            // If we have no Html, we cannot set it
   2.107 -            if (string.IsNullOrEmpty(this.displayState?.Message?.LongMsgFormattedHtml))
   2.108 +            // Set properties difficult to set in XAML
   2.109 +            this.RichTextBoxMessageBody.BackColor = System.Drawing.SystemColors.Window;
   2.110 +
   2.111 +            // If RTF content is not found, set plain text in RTF control
   2.112 +            if (string.IsNullOrEmpty(this.displayState?.Message?.LongMsgFormattedRtf))
   2.113              {
   2.114 -                // If we have no Html, but plain text, wrap it in a basic Html string and display
   2.115 -                string longMsg = this.displayState?.Message?.LongMsg;
   2.116 -                if (string.IsNullOrEmpty(longMsg) == false)
   2.117 -                {
   2.118 -                    displayString = "<!DOCTYPE html><html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8'></head><body><pre>" + longMsg + "</pre></body></html>";
   2.119 -                }
   2.120 +                this.RichTextBoxMessageBody.Text = this.displayState?.Message?.LongMsg;
   2.121              }
   2.122              else
   2.123              {
   2.124 -                // Get Html content and add UTF-8 encoding
   2.125 -                string htmlString = this.displayState.Message.LongMsgFormattedHtml;
   2.126 -                htmlString = htmlString.Replace("<head>", "<head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>");
   2.127 +                // Get mirror RTF file and load it in preview RichTextBoxMessageBody control
   2.128 +                this.RichTextBoxMessageBody.ReadOnly = false;
   2.129 +                try
   2.130 +                {
   2.131 +                    string originalEntryId = this.displayState?.OriginalEntryId;
   2.132  
   2.133 -                // Process embedded images to display them correctly
   2.134 -                if (this.displayState?.Message?.Attachments?.Count > 0)
   2.135 +                    if (string.IsNullOrEmpty(originalEntryId) == false)
   2.136 +                    {
   2.137 +                        // Try to get mirror item
   2.138 +                        Outlook.MailItem mirror = null;
   2.139 +                        try
   2.140 +                        {
   2.141 +                            mirror = PEPMessage.GetMirror(originalEntryId, this.displayState?.Message?.From?.UserName);
   2.142 +                        }
   2.143 +                        catch (Exception ex)
   2.144 +                        {
   2.145 +                            Log.Error("SetRichTextBoxContent: Error getting mirror item. " + ex.ToString());
   2.146 +                        }
   2.147 +
   2.148 +                        // If mirror was found, use it to display content
   2.149 +                        if (mirror != null)
   2.150 +                        {
   2.151 +                            try
   2.152 +                            {
   2.153 +                                // Save mirror in RTF format in local user Temp folder
   2.154 +                                string mirrorRtfTempFilePath = Path.GetTempFileName();
   2.155 +                                mirror.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
   2.156 +                                mirror.SaveAs(mirrorRtfTempFilePath, Outlook.OlSaveAsType.olRTF);
   2.157 +
   2.158 +                                // Load mirror file into the UI
   2.159 +                                this.RichTextBoxMessageBody.LoadFile(mirrorRtfTempFilePath, System.Windows.Forms.RichTextBoxStreamType.RichText);
   2.160 +
   2.161 +                                // Delete lines with fields "From", "OrigEntryId" and others                                                             
   2.162 +                                int origEntryId = this.RichTextBoxMessageBody.Find(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID + ":");
   2.163 +                                if (origEntryId > 1)
   2.164 +                                {
   2.165 +                                    string lastLine = this.RichTextBoxMessageBody.Lines.FirstOrDefault(l => l.Contains(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID + ":"));
   2.166 +
   2.167 +                                    int lastLineIndex =  origEntryId + lastLine.Length;
   2.168 +                                    this.RichTextBoxMessageBody.Select(0, lastLineIndex);
   2.169 +                                    this.RichTextBoxMessageBody.SelectedText = "";
   2.170 +                                }
   2.171 +
   2.172 +                                // Hide caret
   2.173 +                                this.RichTextBoxMessageBody.SelectionStart = this.RichTextBoxMessageBody.Text.Length;
   2.174 +                                this.RichTextBoxMessageBody.SelectionLength = 0;
   2.175 +
   2.176 +                                // Delete Temp file
   2.177 +                                File.Delete(mirrorRtfTempFilePath);
   2.178 +
   2.179 +                                // Set flag to indicate that content has been displayed successfully
   2.180 +                                success = true;
   2.181 +                            }
   2.182 +                            catch (Exception ex)
   2.183 +                            {
   2.184 +                                Log.Error("SetRichTextBoxContent: Error during saving the mail mirror as RTF: " + ex.Message);
   2.185 +                            }
   2.186 +                        }
   2.187 +
   2.188 +                        mirror = null;
   2.189 +                    }
   2.190 +                }
   2.191 +                catch (Exception ex)
   2.192                  {
   2.193 -                    // Look up embedded images in the Html string (<img> tag)
   2.194 -                    MatchCollection embeddedImages = null;
   2.195 +                    Log.Error("SetRichTextBoxContent: Error getting RTF mirror file " + ex.Message);
   2.196 +                }
   2.197 +
   2.198 +                // Backup method in case the above method failed
   2.199 +                if (success == false)
   2.200 +                {
   2.201 +                    // OUT - 21 Show embedded pictures in preview window
   2.202 +                    /*
   2.203 +                        In case of having embedded pictures in HTML body(Email must be sent in HTML format):
   2.204 +
   2.205 +                            1. Parse HTML body to get embedded images names
   2.206 +                            2. Get those images from attachments
   2.207 +                            3. Convert images to WMF format using  [DllImport("gdiplus.dll")] and [DllImport("gdi32.dll")] (SaveMetafile function)
   2.208 +                            4. Get RTF
   2.209 +                                4.1 BackUp Clipboard 
   2.210 +                                4.2 Copy HTML body to WebBrowser Class
   2.211 +                                4.3 Copy from WebBrowser to Clipboard
   2.212 +                                4.4 Get RTF string from Clipboard
   2.213 +                                4.5 Put images in hex format in RTF string
   2.214 +                                4.6 Restore Clipboard
   2.215 +                            5. If operation does not succeed only text without images is shown
   2.216 +
   2.217 +                    */
   2.218 +
   2.219 +                    // If HTML content is not found, set RTF text without images and return
   2.220 +                    string html = this.displayState?.Message?.LongMsgFormattedHtml;
   2.221 +                    if (string.IsNullOrEmpty(html))
   2.222 +                    {
   2.223 +                        this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.224 +                        return;
   2.225 +                    }
   2.226 +
   2.227 +                    // Take Images names from HTML to seek them in Attachments 
   2.228 +                    List<string> imagesNames = new List<string>();
   2.229 +                    List<string> imagesTags = new List<string>();
   2.230                      try
   2.231                      {
   2.232 -                        embeddedImages = Regex.Matches(htmlString, "<img[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
   2.233 -                    }
   2.234 -                    catch (Exception ex)
   2.235 -                    {
   2.236 -                        embeddedImages = null;
   2.237 -                        Log.Error("SetWebBrowserContent: Error getting embedded images. " + ex.ToString());
   2.238 -                    }
   2.239 -
   2.240 -                    // If one or more embedded images were found, process each of them 
   2.241 -                    if (embeddedImages != null)
   2.242 -                    {
   2.243 -                        foreach (Match match in embeddedImages)
   2.244 +                        foreach (Match m in Regex.Matches(html, "<img[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Multiline))
   2.245                          {
   2.246 -                            if (string.IsNullOrEmpty(match?.Value) == false)
   2.247 +                            if (string.IsNullOrEmpty(m.Value) == false)
   2.248                              {
   2.249 -                                try
   2.250 +                                imagesTags.Add(m.Value);
   2.251 +                            }
   2.252 +                        }
   2.253 +                        if (imagesTags.Count > 0)
   2.254 +                        {
   2.255 +                            foreach (string imgTag in imagesTags)
   2.256 +                            {
   2.257 +                                foreach (Match m in Regex.Matches(imgTag, "cid:(.+?)[\"']", RegexOptions.IgnoreCase | RegexOptions.Multiline))
   2.258                                  {
   2.259 -                                    // Get the image's content id name
   2.260 -                                    string cidName = null;
   2.261 -                                    string base64ImageString = null;
   2.262 -                                    string imageName = match.Value;
   2.263 -                                    Match cidMatch = Regex.Match(imageName, "cid:(.+?)[\"']", RegexOptions.IgnoreCase | RegexOptions.Multiline);
   2.264 -                                    if (cidMatch?.Groups?.Count > 0)
   2.265 +                                    if (m.Groups?.Count > 1)
   2.266                                      {
   2.267 -                                        cidName = cidMatch.Groups[1].Value;
   2.268 -                                    }
   2.269 -
   2.270 -                                    // Get the corresponding image attachment and convert to base64 string
   2.271 -                                    foreach (var attachment in this.displayState?.Message?.Attachments)
   2.272 -                                    {
   2.273 -                                        if (attachment?.ContentId?.Equals(cidName) == true)
   2.274 +                                        string src = m.Groups[1].Value;
   2.275 +                                        if (string.IsNullOrEmpty(src) == false)
   2.276                                          {
   2.277 -                                            base64ImageString = Convert.ToBase64String(attachment.Data);
   2.278 -                                            break;
   2.279 +                                            imagesNames.Add(src);
   2.280                                          }
   2.281                                      }
   2.282 -
   2.283 -                                    // Replace the image reference with the base64 encoded image string
   2.284 -                                    if (string.IsNullOrEmpty(base64ImageString) == false)
   2.285 -                                    {
   2.286 -                                        base64ImageString = string.Format("data:image/gif;base64,{0}\"", base64ImageString);
   2.287 -                                        base64ImageString = Regex.Replace(imageName, "cid:(.+?)[\"']", base64ImageString);
   2.288 -                                        htmlString = Regex.Replace(htmlString, imageName, base64ImageString);
   2.289 -                                    }
   2.290 -                                }
   2.291 -                                catch (Exception ex)
   2.292 -                                {
   2.293 -                                    Log.Error("SetWebBrowserContent: Error converting attached image. " + ex.ToString());
   2.294                                  }
   2.295                              }
   2.296                          }
   2.297                      }
   2.298 +                    catch (Exception ex)
   2.299 +                    {
   2.300 +                        Log.Error("SetRichTextBoxContent: Error searching for attached images. " + ex.ToString());
   2.301 +                    }
   2.302 +
   2.303 +                    // If no images were found in HTML, set RTF text without images and return
   2.304 +                    if (imagesNames.Count < 1)
   2.305 +                    {
   2.306 +                        this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.307 +                        return;
   2.308 +                    }
   2.309 +
   2.310 +                    // Look for attachments with image names found in HTML in previous step.
   2.311 +                    // If image is found, convert it to bitmap and add it to List<Bitmap> 
   2.312 +                    List<Bitmap> images = new List<Bitmap>();
   2.313 +                    try
   2.314 +                    {
   2.315 +                        foreach (var imageName in imagesNames)
   2.316 +                        {
   2.317 +                            var attachmentImage = this.displayState?.Message?.Attachments?.SingleOrDefault(i => i.ContentId == imageName);
   2.318 +                            Bitmap img = null;
   2.319 +
   2.320 +                            try
   2.321 +                            {
   2.322 +                                if (attachmentImage != null)
   2.323 +                                {
   2.324 +                                    img = (new ImageConverter()).ConvertFrom(attachmentImage.Data) as Bitmap;
   2.325 +                                }
   2.326 +                            }
   2.327 +                            catch (Exception ex)
   2.328 +                            {
   2.329 +                                img = null;
   2.330 +                                Log.Verbose("SetRichTextBoxContent: Error converting attachment image to Bitmap. " + ex.ToString());
   2.331 +                            }
   2.332 +
   2.333 +                            if (img != null)
   2.334 +                            {
   2.335 +                                images.Add(img);
   2.336 +                            }
   2.337 +                        }
   2.338 +                    }
   2.339 +                    catch (Exception ex)
   2.340 +                    {
   2.341 +                        Log.Error("SetRichTextBoxContent: Error searching for attached images. " + ex.ToString());
   2.342 +                    }
   2.343 +
   2.344 +                    // If no images were found, set RTF text without images and return
   2.345 +                    if (images.Count < 1)
   2.346 +                    {
   2.347 +                        this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.348 +                        return;
   2.349 +                    }
   2.350 +
   2.351 +                    /* 
   2.352 +                            1. SaveMetafile function embeds bitmap image in WMF format using C++ functions from gdiplus.dll, gdi32.dll and converts it to byte[] 
   2.353 +                            2. Byte array is saved to hard drive in local Temp folder.
   2.354 +                            3. SaveMetafile function returns path+file name to temporary stored on hard drive WMF image file in byte array
   2.355 +                     */
   2.356 +                    List<string> wmfFileNames = new List<string>();
   2.357 +                    foreach (Bitmap img in images)
   2.358 +                    {
   2.359 +                        try
   2.360 +                        {
   2.361 +                            string wmfFileName = SaveMetafile(img);
   2.362 +
   2.363 +                            if (string.IsNullOrEmpty(wmfFileName) == false)
   2.364 +                            {
   2.365 +                                wmfFileNames.Add(wmfFileName);
   2.366 +                            }
   2.367 +                        }
   2.368 +                        catch (Exception ex)
   2.369 +                        {
   2.370 +                            Log.Error("SetRichTextBoxContent: Error converting Bitmap image: " + ex.Message);
   2.371 +                        }
   2.372 +                    }
   2.373 +
   2.374 +                    // If none of the images could be converted, set RTF text without images and return
   2.375 +                    if (wmfFileNames.Count < 1)
   2.376 +                    {
   2.377 +                        Log.Verbose("SetRichTextBoxContent: No images in WMF format available.");
   2.378 +                        this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.379 +                        return;
   2.380 +                    }
   2.381 +
   2.382 +                    // Create WebBrowser and copy HTML to it
   2.383 +                    using (System.Windows.Forms.WebBrowser wb = new System.Windows.Forms.WebBrowser())
   2.384 +                    {
   2.385 +                        try
   2.386 +                        {
   2.387 +                            wb.Navigate("about:blank");
   2.388 +                            wb.Document.Write(html);
   2.389 +                        }
   2.390 +                        catch (Exception ex)
   2.391 +                        {
   2.392 +                            Log.Error("SetRichTextBoxContent: Error writing HTML to web browser. " + ex.ToString());
   2.393 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.394 +                            return;
   2.395 +                        }
   2.396 +
   2.397 +                        // Back up data from clipboard
   2.398 +                        object rtfFromClipboard = null;
   2.399 +                        string textFromClipboard = null;
   2.400 +                        BitmapSource imageFromClipboard = null;
   2.401 +                        try
   2.402 +                        {
   2.403 +                            IDataObject dataFromClipboard = Clipboard.GetDataObject();
   2.404 +
   2.405 +                            if (dataFromClipboard.GetDataPresent(DataFormats.Rtf))
   2.406 +                            {
   2.407 +                                rtfFromClipboard = dataFromClipboard.GetData(DataFormats.Rtf);
   2.408 +                            }
   2.409 +
   2.410 +                            if (Clipboard.ContainsText())
   2.411 +                            {
   2.412 +                                textFromClipboard = Clipboard.GetText();
   2.413 +                            }
   2.414 +
   2.415 +                            if (Clipboard.ContainsImage())
   2.416 +                            {
   2.417 +                                imageFromClipboard = Clipboard.GetImage();
   2.418 +                            }
   2.419 +                        }
   2.420 +                        catch (Exception ex)
   2.421 +                        {
   2.422 +                            Log.Error("SetRichTextBoxContent: Error backing up clipboard. " + ex.ToString());
   2.423 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.424 +                            return;
   2.425 +                        }
   2.426 +
   2.427 +                        // Copy data from WebBrowser to Clipboard
   2.428 +                        try
   2.429 +                        {
   2.430 +                            wb.Document.ExecCommand("SelectAll", false, null);
   2.431 +                            wb.Document.ExecCommand("Copy", false, null);
   2.432 +                        }
   2.433 +                        catch (Exception ex)
   2.434 +                        {
   2.435 +                            Log.Error("SetRichTextBoxContent: Error copying from web browser to clipboard. " + ex.ToString());
   2.436 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.437 +                            return;
   2.438 +                        }
   2.439 +
   2.440 +                        string rtf = string.Empty;
   2.441 +
   2.442 +                        try
   2.443 +                        {
   2.444 +                            // Get RTF from Clipboard
   2.445 +                            rtf = System.Windows.Forms.Clipboard.GetData(DataFormats.Rtf) as string;
   2.446 +
   2.447 +                            // Restore Clipboard backup
   2.448 +                            if (rtfFromClipboard != null)
   2.449 +                            {
   2.450 +                                Clipboard.SetData(DataFormats.Rtf, rtfFromClipboard);
   2.451 +                            }
   2.452 +                            else
   2.453 +                            {
   2.454 +                                if (imageFromClipboard != null)
   2.455 +                                {
   2.456 +                                    Clipboard.SetImage(imageFromClipboard);
   2.457 +                                }
   2.458 +                                if (textFromClipboard != null)
   2.459 +                                {
   2.460 +                                    Clipboard.SetText(textFromClipboard);
   2.461 +                                }
   2.462 +                            }
   2.463 +                        }
   2.464 +                        catch (Exception ex)
   2.465 +                        {
   2.466 +                            Log.Error("SetRichTextBoxContent: Error getting rtf from clipboard. " + ex.ToString());
   2.467 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.468 +
   2.469 +                            // If Exception was caused by another application which uses clipboard than GetOpenClipboardWindowText() will return information of this application. Uses user32.dll 
   2.470 +                            var msg = this.GetOpenClipboardWindowText();
   2.471 +                            Log.Error("Clipboard error: " + msg);
   2.472 +                            return;
   2.473 +                        }
   2.474 +
   2.475 +                        try
   2.476 +                        {
   2.477 +                            // Replace WMF picture headers with RtfPicturePlaceHolder  
   2.478 +                            rtf = ReplaceWmfHeaders(rtf);
   2.479 +                        }
   2.480 +                        catch (Exception ex)
   2.481 +                        {
   2.482 +                            Log.Error("SetRichTextBoxContent: Error replacing WMF headers. " + ex.ToString());
   2.483 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.484 +                            return;
   2.485 +                        }
   2.486 +
   2.487 +                        try
   2.488 +                        {
   2.489 +                            //  Replace RtfPicturePlaceHolder with images hex strings taken from files                        
   2.490 +                            for (int i = 0; i < wmfFileNames.Count; i++)
   2.491 +                            {
   2.492 +                                ReplaceFirstFromFile(ref rtf, RtfPicturePlaceHolder, wmfFileNames[i]);
   2.493 +                            }
   2.494 +                        }
   2.495 +                        catch (Exception ex)
   2.496 +                        {
   2.497 +                            Log.Error("SetRichTextBoxContent: Error replacing RtfPicturePlaceHolder. " + ex.ToString());
   2.498 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.499 +                            return;
   2.500 +                        }
   2.501 +
   2.502 +                        if (string.IsNullOrEmpty(rtf))
   2.503 +                        {
   2.504 +                            this.RichTextBoxMessageBody.Rtf = this.displayState?.Message?.LongMsgFormattedRtf;
   2.505 +                            return;
   2.506 +                        }
   2.507 +
   2.508 +                        // Assign RichTextBoxMessageBody.Rtf with rtf
   2.509 +                        this.RichTextBoxMessageBody.Rtf = rtf;
   2.510 +                    }
   2.511                  }
   2.512 -
   2.513 -                displayString = htmlString;
   2.514              }
   2.515  
   2.516 -            // If we have something to display, pass it to the web browser
   2.517 -            if (string.IsNullOrEmpty(displayString) == false)
   2.518 +            return;
   2.519 +        }
   2.520 +        /// <summary>
   2.521 +        /// Replace empty WMF picture headers with placeholder RtfPicturePlaceHolder in order to replace placeholder with WMF hex strings  
   2.522 +        /// </summary>
   2.523 +        private string ReplaceWmfHeaders(string rtf)
   2.524 +        {
   2.525 +            if (string.IsNullOrEmpty(rtf))
   2.526              {
   2.527 -                this.WebBrowser.NavigateToString(displayString);
   2.528 +                return null;
   2.529              }
   2.530 +            List<int> picturesStartIndexes = new List<int>();
   2.531 +            for (int index = 0; ; index += RtfPictureTag.Length)
   2.532 +            {
   2.533 +                index = rtf.IndexOf(RtfPictureTag, index);
   2.534 +                if (index == -1)
   2.535 +                {
   2.536 +                    break;
   2.537 +                }
   2.538 +                picturesStartIndexes.Add(index);
   2.539 +            }
   2.540 +
   2.541 +            List<string> stringsToReplace = new List<string>();
   2.542 +
   2.543 +            foreach (int startIndex in picturesStartIndexes)
   2.544 +            {
   2.545 +                int pictureStartIndex = rtf.IndexOf("010009000003", startIndex);
   2.546 +                int PictureEndIndex = rtf.IndexOf("}", pictureStartIndex);
   2.547 +                if (pictureStartIndex < 0 || PictureEndIndex < 0)
   2.548 +                {
   2.549 +                    continue;
   2.550 +                }
   2.551 +                string rtfPicture = rtf.Substring(pictureStartIndex, PictureEndIndex - pictureStartIndex);
   2.552 +                stringsToReplace.Add(rtfPicture);
   2.553 +            }
   2.554 +            stringsToReplace.ForEach(s => rtf = rtf.Replace(s, RtfPicturePlaceHolder));
   2.555 +
   2.556 +            return rtf;
   2.557 +        }
   2.558 +
   2.559 +        /// <summary>
   2.560 +        /// Return process name which uses Clipboard        
   2.561 +        /// </summary>
   2.562 +        private string GetOpenClipboardWindowText()
   2.563 +        {
   2.564 +            IntPtr hwnd = NativeMethods.GetOpenClipboardWindow();
   2.565 +            StringBuilder sb = new StringBuilder(501);
   2.566 +            NativeMethods.GetWindowText(hwnd, sb, 500);
   2.567 +            return sb.ToString();
   2.568 +        }
   2.569 +
   2.570 +        /// <summary>
   2.571 +        /// Replace first occurrence in string with WMF image stored on hard drive. 
   2.572 +        /// </summary>
   2.573 +        /// <param name="text">string where to search in</param>
   2.574 +        /// <param name="search">string to search for</param>
   2.575 +        /// <param name="fileName">File name containing string to replace with</param>
   2.576 +        private void ReplaceFirstFromFile(ref string text, string search, string fileName)
   2.577 +        {
   2.578 +            try
   2.579 +            {
   2.580 +                int pos = text.IndexOf(search);
   2.581 +                if (pos < 0)
   2.582 +                {
   2.583 +                    return;
   2.584 +                }
   2.585 +                StringBuilder sb = new StringBuilder(text.Substring(0, pos));
   2.586 +
   2.587 +                // Read WMF image from file and convert it to hex strings which is inserted in RTF
   2.588 +                SoapHexBinary shb = new SoapHexBinary(File.ReadAllBytes(fileName));
   2.589 +                sb.Append(shb.ToString());
   2.590 +                shb = null;
   2.591 +                sb.Append(text.Substring(pos + search.Length));
   2.592 +                text = null;
   2.593 +                text = sb.ToString();
   2.594 +                sb = null;
   2.595 +            }
   2.596 +            catch (Exception ex)
   2.597 +            {
   2.598 +                Log.Error("ReplaceFirstFromFile: Error. " + ex.ToString());
   2.599 +            }
   2.600 +        }
   2.601 +
   2.602 +        /// <summary>
   2.603 +        /// Make Bitmap image embedded in Metafile format and save it to temporary folder
   2.604 +        /// GDI C++ libraries used. 
   2.605 +        /// </summary>
   2.606 +        private string SaveMetafile(Bitmap image)
   2.607 +        {
   2.608 +            Metafile metafile = null;
   2.609 +
   2.610 +            try
   2.611 +            {
   2.612 +                using (Graphics g = Graphics.FromImage(image))
   2.613 +                {
   2.614 +                    IntPtr hDC = g.GetHdc();
   2.615 +                    metafile = new Metafile(hDC, EmfType.EmfOnly);
   2.616 +                    g.ReleaseHdc(hDC);
   2.617 +                }
   2.618 +            }
   2.619 +            catch
   2.620 +            {
   2.621 +                Bitmap tempBitmap = new Bitmap(image.Width, image.Height);
   2.622 +                using (Graphics g = Graphics.FromImage(tempBitmap))
   2.623 +                {
   2.624 +                    g.DrawImage(image, 0, 0);
   2.625 +                    IntPtr hDC = g.GetHdc();
   2.626 +                    metafile = new Metafile(hDC, EmfType.EmfOnly);
   2.627 +                    g.ReleaseHdc(hDC);
   2.628 +                }
   2.629 +            }
   2.630 +
   2.631 +            using (Graphics g = Graphics.FromImage(metafile))
   2.632 +            {
   2.633 +                g.DrawImage(image, 0, 0);
   2.634 +            }
   2.635 +
   2.636 +            IntPtr hEmf = metafile.GetHenhmetafile();
   2.637 +            uint bufferSize = NativeMethods.GdipEmfToWmfBits(hEmf, 0, null, MM_ANISOTROPIC, NativeMethods.EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
   2.638 +            byte[] buffer = new byte[bufferSize];
   2.639 +            NativeMethods.GdipEmfToWmfBits(hEmf, bufferSize, buffer, MM_ANISOTROPIC, NativeMethods.EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
   2.640 +            NativeMethods.DeleteEnhMetaFile(hEmf);
   2.641 +
   2.642 +            string fileName = Guid.NewGuid().ToString();
   2.643 +            string tempFolderAndFileName = Path.Combine(this.TempFolder, fileName);
   2.644 +            try
   2.645 +            {
   2.646 +                File.WriteAllBytes(tempFolderAndFileName, buffer);
   2.647 +                buffer = null;
   2.648 +            }
   2.649 +            catch (Exception ex)
   2.650 +            {
   2.651 +                Log.Error("SaveMetaFile: Error writing to disk. " + ex.ToString());
   2.652 +                return null;
   2.653 +            }
   2.654 +
   2.655 +            return tempFolderAndFileName;
   2.656 +        }
   2.657 +
   2.658 +        /// <summary>
   2.659 +        ///  Creates temporary folder using Path.GetTempFileName
   2.660 +        /// </summary>
   2.661 +        private string GetTempFileFolder()
   2.662 +        {
   2.663 +            // Create temp directory
   2.664 +            string tempFileName = Path.GetTempFileName();
   2.665 +            string tempDir = tempFileName + ".dir";
   2.666 +            Directory.CreateDirectory(tempDir);
   2.667 +
   2.668 +            // GetTempFileName() creates an empty file which is not needed
   2.669 +            try
   2.670 +            {
   2.671 +                File.Delete(tempFileName);
   2.672 +            }
   2.673 +            catch (Exception ex)
   2.674 +            {
   2.675 +                Log.Error("Could not delete temp file. " + ex.ToString());
   2.676 +            }
   2.677 +
   2.678 +            return tempDir;
   2.679          }
   2.680  
   2.681          /**************************************************************
   2.682 @@ -217,7 +687,7 @@
   2.683                   * Any changes to the LongMsgFormattedRtf property directly are ignored.
   2.684                   * However, setting the Message is the only use case in code.
   2.685                   */
   2.686 -                this.SetWebBrowserContent();
   2.687 +                this.SetRichTextBoxContent();
   2.688              }
   2.689  
   2.690              this.PropertyChanged?.Invoke(this, e);
   2.691 @@ -252,6 +722,20 @@
   2.692          }
   2.693  
   2.694          /// <summary>
   2.695 +        /// Event handler that occurs when a link is clicked within the body rich text box.
   2.696 +        /// </summary>
   2.697 +        private void RichTextBoxMessageBody_LinkClicked(object sender, System.Windows.Forms.LinkClickedEventArgs e)
   2.698 +        {
   2.699 +            try
   2.700 +            {
   2.701 +                System.Diagnostics.Process.Start(e.LinkText);
   2.702 +            }
   2.703 +            catch { }
   2.704 +
   2.705 +            return;
   2.706 +        }
   2.707 +
   2.708 +        /// <summary>
   2.709          /// Event handler for when an attachment button is clicked.
   2.710          /// </summary>
   2.711          private void ButtonAttachment_Click(object sender, RoutedEventArgs e)
   2.712 @@ -266,9 +750,6 @@
   2.713              return;
   2.714          }
   2.715  
   2.716 -        /// <summary>
   2.717 -        /// Event handler for when an attachment button is doubleclicked.
   2.718 -        /// </summary>
   2.719          private void ButtonAttachment_DoubleClick(object sender, RoutedEventArgs e)
   2.720          {
   2.721              ContentControl contentControl = sender as ContentControl;
   2.722 @@ -318,7 +799,7 @@
   2.723                  result = dialog.ShowDialog();
   2.724  
   2.725                  if (result == System.Windows.Forms.DialogResult.OK)
   2.726 -                {
   2.727 +                {                    
   2.728                      attach.SaveAs(dialog.FileName, true);
   2.729                  }
   2.730              }
   2.731 @@ -326,6 +807,24 @@
   2.732              return;
   2.733          }
   2.734  
   2.735 +        /// <summary>
   2.736 +        /// Event handler that hides caret in RichTextBoxMessageBody
   2.737 +        /// </summary>
   2.738 +        /// <param name="sender"></param>
   2.739 +        /// <param name="e"></param>
   2.740 +        private void RichTextBoxMessageBody_HideCaret(object sender, EventArgs e)
   2.741 +        {
   2.742 +            try
   2.743 +            {
   2.744 +                NativeMethods.HideCaret(this.RichTextBoxMessageBody.Handle);
   2.745 +            }
   2.746 +            catch (Exception ex)
   2.747 +            {
   2.748 +                Log.Error("RichTextBoxMessageBody_HideCaret. Error when calling HideCaret: " + ex.Message);
   2.749 +            }
   2.750 +
   2.751 +        }
   2.752 +
   2.753          /**************************************************************
   2.754           * 
   2.755           * Sub-classes
   2.756 @@ -538,7 +1037,7 @@
   2.757  
   2.758                      this._CcRecipients = recipientText;
   2.759                      this.RaisePropertyChangedEvent(nameof(this.CcRecipients));
   2.760 -                }
   2.761 +                }              
   2.762  
   2.763                  // From recipient
   2.764                  this._FromRecipient = this._Message?.From?.UserName;