Toasts – Blame information for rev 55

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