Toasts – Blame information for rev 57

Subversion Repositories:
Rev:
Rev Author Line No. Line
41 office 1 // =====COPYRIGHT=====
2 // Code originally retrieved from http://www.vbforums.com/showthread.php?t=547778 - no license information supplied
3 // =====COPYRIGHT=====
4  
5 using Markdig.Extensions.Tables;
6 using System;
7 using System.Collections.Concurrent;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Drawing;
11 using System.IO;
12 using System.Linq;
13 using System.Media;
55 office 14 using System.Net;
49 office 15 using System.Net.Http;
41 office 16 using System.Runtime.InteropServices;
53 office 17 using System.Runtime.Remoting.Messaging;
18 using System.Runtime.Serialization;
56 office 19 using System.Runtime.Serialization.Formatters.Binary;
41 office 20 using System.Threading;
21 using System.Threading.Tasks;
22 using System.Windows.Forms;
53 office 23 using System.Xml.Linq;
41 office 24 using TheArtOfDev.HtmlRenderer.WinForms;
25 using Toasts.Properties;
53 office 26 using Toasts.Utilities;
27  
41 office 28 namespace Toasts
29 {
44 office 30 public partial class ToastForm : Form
31 {
32 #region Public Fields and Properties
49 office 33 public string UserAgent { get; set; } = string.Empty;
34  
44 office 35 public bool EnableChime { get; set; } = true;
36  
37 public int LingerTime { get; set; } = 5000;
38  
39 public int AnimationDuration { get; set; } = 500;
40  
41 public string ContentType { get; internal set; } = "text/plain";
42  
43 public byte[] Chime
44 {
49 office 45 get
46 {
47 // offer the default built-in chime
48 if(_chime == null)
49 {
50 using (var memoryStream = new MemoryStream())
51 {
52 Resources.normal.CopyTo(memoryStream);
53  
54 memoryStream.Position = 0L;
55  
56 return memoryStream.ToArray();
57 }
58 }
59  
60 return _chime;
61 }
44 office 62 set
63 {
64 if (value is null)
65 {
66 return;
67 }
68  
69 _chime = value;
70 }
71 }
72  
55 office 73 public Proxy Proxy { get; set; }
49 office 74  
55 office 75 public bool EnablePin { get; set; }
76  
77 public Point PinPoint { get; set; }
78  
44 office 79 /// <summary>
80 ///
81 /// </summary>
82 /// <remarks>clone the image for safety</remarks>
83 public Image Image
84 {
57 office 85 get
86 {
87 if(_image == null)
88 {
89 return null;
90 }
91  
92 var binaryFormatter = new BinaryFormatter();
93 using (var memoryStream = new MemoryStream())
94 {
95 binaryFormatter.Serialize(memoryStream, _image);
96 memoryStream.Position = 0L;
97 return (Bitmap)binaryFormatter.Deserialize(memoryStream);
98 }
99 }
53 office 100 set
44 office 101 {
53 office 102 if (value == _image)
42 office 103 {
53 office 104 return;
105 }
42 office 106  
56 office 107 var binaryFormatter = new BinaryFormatter();
108 using (var memoryStream = new MemoryStream())
109 {
110 binaryFormatter.Serialize(memoryStream, value);
111 memoryStream.Position = 0L;
112 _image = (Bitmap)binaryFormatter.Deserialize(memoryStream);
113 }
44 office 114 }
43 office 115 }
116  
44 office 117 #endregion
118  
49 office 119 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
44 office 120  
121 private static readonly object OpenNotificationsLock = new object();
122  
53 office 123 private static readonly List<ToastForm> OpenNotifications = new List<ToastForm>();
44 office 124  
53 office 125 private TaskCompletionSource<object> _handleCreatedTaskCompletionSource;
126  
49 office 127 private bool _toastDetached;
44 office 128  
49 office 129 private object _toastDetachedLock = new object();
44 office 130  
53 office 131 private Image _image;
132  
44 office 133 private bool _mouseDown;
49 office 134  
44 office 135 private Point _lastLocation;
136  
55 office 137 private static HttpClient _httpClient;
44 office 138  
53 office 139 private static object _httpClientDefaultRequestHeadersLock = new object();
140  
49 office 141 private System.Timers.Timer _toastTimer;
142  
143 private readonly int _screenWidth;
144  
145 private readonly int _screenHeight;
146  
147 private byte[] _chime;
148  
44 office 149 protected override bool ShowWithoutActivation => true;
49 office 150  
44 office 151 protected override CreateParams CreateParams
152 {
153 get
154 {
155  
156 var baseParams = base.CreateParams;
157  
158 //const int WS_EX_NOACTIVATE = 0x08000000;
159 const int WS_EX_TOOLWINDOW = 0x00000080;
160 const int WS_EX_TOPMOST = 0x00000008;
161 baseParams.ExStyle |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
162  
163 return baseParams;
164 }
165 }
166  
49 office 167 #endregion
43 office 168  
49 office 169 #region Natives
44 office 170 [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
171 public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
172 #endregion
173  
174 #region Constructors, Destructors and Finalizers
175  
176 private ToastForm()
177 {
178 InitializeComponent();
179  
53 office 180 _handleCreatedTaskCompletionSource = new TaskCompletionSource<object>();
181  
182 HandleCreated += ToastForm_HandleCreated;
183  
44 office 184 _screenWidth = Screen.PrimaryScreen.WorkingArea.Width;
185 _screenHeight = Screen.PrimaryScreen.WorkingArea.Height;
186 }
187  
53 office 188  
44 office 189 /// <summary>
190 /// Display a toast with a title, body and a logo indefinitely on screen.
191 /// </summary>
192 /// <param name="title">the toast title</param>
193 /// <param name="body">the toast body</param>
194 public ToastForm(string title, string body) : this()
195 {
196 labelTitle.Text = title;
197 htmlPanel1.Text = body;
55 office 198  
199 var httpClientHandler = new HttpClientHandler
200 {
201 // mono does not implement this
202 //SslProtocols = SslProtocols.Tls12
203 };
204  
205 if (Proxy != null && Proxy.Enable)
206 {
207 httpClientHandler.Proxy = new WebProxy(Proxy.Url, false, new string[] { },
208 new NetworkCredential(Proxy.Username, Proxy.Password));
209 }
210  
211 _httpClient = new HttpClient(httpClientHandler);
44 office 212 }
213  
214 /// <summary>
215 /// Clean up any resources being used.
216 /// </summary>
217 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
218 protected override void Dispose(bool disposing)
219 {
220 if (disposing && components != null)
221 {
53 office 222 HandleCreated -= ToastForm_HandleCreated;
223  
224 if (_toastTimer != null)
225 {
226 _toastTimer.Dispose();
227 _toastTimer = null;
228 }
229  
230 if (_image != null)
231 {
232 _image.Dispose();
233 _image = null;
234 }
235  
44 office 236 components.Dispose();
237 }
238  
53 office 239 base.Dispose(disposing);
240 }
241  
242 #endregion
243  
244 #region Event Handlers
245  
246 private void ToastForm_HandleCreated(object sender, EventArgs e)
247 {
248 _handleCreatedTaskCompletionSource.TrySetResult(new { });
249 }
250  
251 private void Toast_MouseEnter(object sender, EventArgs e)
252 {
253 if (_toastTimer == null)
44 office 254 {
53 office 255 return;
44 office 256 }
257  
53 office 258 _toastTimer.Stop();
259 }
260  
261 private void Toast_MouseLeave(object sender, EventArgs e)
262 {
263 if (_toastTimer == null)
44 office 264 {
53 office 265 return;
44 office 266 }
267  
53 office 268 _toastTimer.Start();
44 office 269 }
270  
271 private void panel1_Click(object sender, EventArgs e)
272 {
273 try
274 {
45 office 275 Clipboard.SetText(htmlPanel1.Text);
276 }
277 catch
278 {
279 Debug.WriteLine("Could not copy to clipboard.");
280 }
281 }
282  
283 private void panel2_Click(object sender, EventArgs e)
284 {
53 office 285 lock (OpenNotificationsLock)
45 office 286 {
53 office 287 this.InvokeIfRequired(form =>
44 office 288 {
53 office 289 form.Close();
290 });
44 office 291 }
292 }
293  
294 private void Toast_Click(object sender, EventArgs e)
295 {
296 lock (_toastDetachedLock)
297 {
298 if (!_toastDetached)
299 {
300 return;
301 }
302  
303 BringToFront();
304 }
305 }
306  
53 office 307 private async void ToastForm_Load(object sender, EventArgs e)
308 {
44 office 309 if (EnableChime)
310 {
53 office 311 try
44 office 312 {
53 office 313 using (var memoryStream = new MemoryStream(Chime))
43 office 314 {
53 office 315 using (var player = new SoundPlayer(memoryStream))
43 office 316 {
53 office 317 player.Play();
44 office 318 }
319 }
53 office 320 }
321 catch
322 {
323 Debug.WriteLine("Sound file could not be played.");
324 }
44 office 325 }
326  
53 office 327 await _handleCreatedTaskCompletionSource.Task;
328  
329 // if no image is provided, collapse the panel for the extra space
330 // otherwise display the image
331 switch (_image == null)
44 office 332 {
53 office 333 case true:
334 splitContainer1.Panel1Collapsed = true;
335 break;
336 default:
337 imageBox.BackgroundImage = _image;
338 break;
44 office 339  
53 office 340 }
341  
342 // compute notification height from body
343 var maxWidth = tableLayoutPanel4.Width;
344  
345 switch (ContentType)
346 {
347 case "text/markdown":
348 var htmlDocument = new HtmlAgilityPack.HtmlDocument();
349 var panelText = htmlPanel1.Text;
350 if (!string.IsNullOrEmpty(panelText))
44 office 351 {
53 office 352 htmlDocument.LoadHtml(panelText);
353 if (htmlDocument.DocumentNode != null && htmlDocument.DocumentNode.Descendants().Any())
44 office 354 {
53 office 355 var imgNodes = htmlDocument.DocumentNode.SelectNodes("//img");
356 if (imgNodes != null && imgNodes.Any())
357 {
358 foreach (var node in imgNodes)
44 office 359 {
53 office 360 node.SetAttributeValue("style", $"max-width: {maxWidth}px");
44 office 361 }
362 }
363 }
364  
53 office 365 htmlPanel1.Text = htmlDocument.DocumentNode.WriteTo();
44 office 366 }
53 office 367 break;
368 default:
369 break;
370 }
44 office 371  
53 office 372 using (var image = HtmlRender.RenderToImage(htmlPanel1.Text, maxWidth, 0, Color.Empty))
373 {
374 var height = image.Height + labelTitle.Height;
375 if (height > Height)
376 {
377 Height = height;
378 }
379 }
44 office 380  
53 office 381 if (EnablePin)
382 {
383 lock (OpenNotificationsLock)
384 {
385 this.InvokeIfRequired(form =>
44 office 386 {
53 office 387 // set the location of the toast
388 form.Location = new Point(PinPoint.X, PinPoint.Y);
389 });
390 }
44 office 391  
53 office 392 // remove top-most
393 SetWindowPos(Handle, -2, 0, 0, 0, 0, 0x0001 | 0x0002);
44 office 394  
53 office 395 return;
396 }
44 office 397  
53 office 398 lock (OpenNotificationsLock)
399 {
400 foreach (var openForm in new List<ToastForm>(OpenNotifications))
401 {
402 if (openForm == null || openForm.IsDisposed)
403 {
404 OpenNotifications.Remove(openForm);
405 continue;
406 }
407  
408 openForm.InvokeIfRequired(form =>
409 {
54 office 410 // if the form will end up off-screen, then don't even bother moving it
53 office 411 foreach (Screen screen in Screen.AllScreens)
44 office 412 {
53 office 413 if (!screen.WorkingArea.Contains(new Rectangle(form.Left, form.Top - Height, form.Width, form.Height)))
44 office 414 {
53 office 415 form.Close();
416 OpenNotifications.Remove(form);
54 office 417  
418 return;
44 office 419 }
53 office 420 }
54 office 421  
53 office 422 form.Top -= Height;
54 office 423  
53 office 424 });
425 }
44 office 426  
53 office 427 // set the location of the toast
428 Location = new Point(_screenWidth - Width, _screenHeight - Height);
44 office 429  
53 office 430 OpenNotifications.Add(this);
431 // show the form
432 //Opacity = 1;
433 }
44 office 434  
53 office 435 // set up the timer when the notification should be removed
436 _toastTimer = new System.Timers.Timer { Enabled = true, AutoReset = false, Interval = LingerTime };
437 _toastTimer.Elapsed += ToastTimer_Elapsed;
438 _toastTimer.Start();
44 office 439 }
440  
49 office 441 private async void HtmlPanel1_ImageLoad(object sender, TheArtOfDev.HtmlRenderer.Core.Entities.HtmlImageLoadEventArgs e)
442 {
53 office 443 if (!string.IsNullOrEmpty(UserAgent))
49 office 444 {
53 office 445 lock (_httpClientDefaultRequestHeadersLock)
446 {
447 if (!_httpClient.DefaultRequestHeaders.Contains("User-Agent"))
448 {
449 _httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent);
450 }
451 }
49 office 452 }
453  
454 try
455 {
53 office 456 using (var response = await _httpClient.GetAsync(e.Src))
49 office 457 {
458 using (var responseStream = await response.Content.ReadAsStreamAsync())
459 {
460 var image = Image.FromStream(responseStream);
461  
462 e.Callback(image);
463 e.Handled = true;
464 }
465 }
466 }
467 catch
468 {
469 Debug.WriteLine("Error downloading image.");
470 }
471 }
472  
44 office 473 private void ToastForm_FormClosed(object sender, FormClosedEventArgs e)
474 {
53 office 475 lock (OpenNotificationsLock)
44 office 476 {
53 office 477 OpenNotifications.Remove(this);
44 office 478 }
479 }
480  
53 office 481 private void ToastTimer_Elapsed(object sender, EventArgs e)
44 office 482 {
483 if (IsDisposed)
484 {
485 return;
486 }
487  
488 lock (_toastDetachedLock)
489 {
490 if (_toastDetached)
491 {
492 return;
493 }
494 }
495  
496 try
497 {
498 _toastTimer.Stop();
499  
53 office 500 lock (OpenNotificationsLock)
44 office 501 {
53 office 502 this.InvokeIfRequired(form =>
44 office 503 {
53 office 504 form.Close();
505 });
506 }
44 office 507 }
508 catch
509 {
510 Debug.WriteLine("Error in timer elapsed event.");
511 }
512 }
513  
514 private void labelTitle_MouseDown(object sender, MouseEventArgs e)
515 {
516 _mouseDown = true;
517 _lastLocation = e.Location;
518 }
519  
520 private void labelTitle_MouseMove(object sender, MouseEventArgs e)
521 {
522 if (!_mouseDown)
523 {
524 return;
525  
526 }
527  
528 Location = new Point((Location.X - _lastLocation.X) + e.X, (Location.Y - _lastLocation.Y) + e.Y);
529  
530 Update();
531  
532 lock (_toastDetachedLock)
533 {
534 if (_toastDetached)
535 {
536 return;
537 }
538  
539 _toastDetached = true;
540  
541 _toastTimer.Elapsed -= ToastTimer_Elapsed;
542 _toastTimer.Stop();
543 _toastTimer.Dispose();
53 office 544 _toastTimer = null;
44 office 545  
53 office 546 lock (OpenNotificationsLock)
44 office 547 {
53 office 548 OpenNotifications.Remove(this);
549 }
44 office 550  
551 // remove top-most
552 SetWindowPos(Handle, -2, 0, 0, 0, 0, 0x0001 | 0x0002);
553 }
554 }
555  
556 private void labelTitle_MouseUp(object sender, MouseEventArgs e)
557 {
558 _mouseDown = false;
559 }
560  
561  
562 #endregion
563  
564 #region Private Methods
565  
566 #endregion
41 office 567 }
1 office 568 }