Toasts – Blame information for rev 54

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