Toasts – Rev 58
?pathlinks?
// =====COPYRIGHT=====
// Code originally retrieved from http://www.vbforums.com/showthread.php?t=547778 - no license information supplied
// =====COPYRIGHT=====
using Markdig.Extensions.Tables;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Media;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using TheArtOfDev.HtmlRenderer.WinForms;
using Toasts.Properties;
using Toasts.Utilities;
namespace Toasts
{
public partial class ToastForm : Form
{
#region Public Fields and Properties
public string UserAgent { get; set; } = string.Empty;
public bool EnableChime { get; set; } = true;
public int LingerTime { get; set; } = 5000;
public int AnimationDuration { get; set; } = 500;
public string ContentType { get; internal set; } = "text/plain";
public byte[] Chime
{
get
{
// offer the default built-in chime
if(_chime == null)
{
using (var memoryStream = new MemoryStream())
{
Resources.normal.CopyTo(memoryStream);
memoryStream.Position = 0L;
return memoryStream.ToArray();
}
}
return _chime;
}
set
{
if (value is null)
{
return;
}
_chime = value;
}
}
public Proxy Proxy { get; set; }
public bool EnablePin { get; set; }
public Point PinPoint { get; set; }
/// <summary>
///
/// </summary>
/// <remarks>clone the image for safety</remarks>
public Image Image
{
get
{
if(_image == null)
{
return null;
}
var binaryFormatter = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, _image);
memoryStream.Position = 0L;
return (Bitmap)binaryFormatter.Deserialize(memoryStream);
}
}
set
{
if (value == _image)
{
return;
}
var binaryFormatter = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, value);
memoryStream.Position = 0L;
_image = (Bitmap)binaryFormatter.Deserialize(memoryStream);
}
}
}
#endregion
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
private static readonly object OpenNotificationsLock = new object();
private static readonly List<ToastForm> OpenNotifications = new List<ToastForm>();
private TaskCompletionSource<object> _handleCreatedTaskCompletionSource;
private bool _toastDetached;
private object _toastDetachedLock = new object();
private Image _image;
private bool _mouseDown;
private Point _lastLocation;
private static HttpClient _httpClient;
private static object _httpClientDefaultRequestHeadersLock = new object();
private System.Timers.Timer _toastTimer;
private readonly int _screenWidth;
private readonly int _screenHeight;
private byte[] _chime;
private string _title;
private string _body;
protected override bool ShowWithoutActivation => true;
protected override CreateParams CreateParams
{
get
{
var baseParams = base.CreateParams;
//const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOOLWINDOW = 0x00000080;
const int WS_EX_TOPMOST = 0x00000008;
baseParams.ExStyle |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
return baseParams;
}
}
#endregion
#region Natives
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
#endregion
#region Constructors, Destructors and Finalizers
private ToastForm()
{
InitializeComponent();
_handleCreatedTaskCompletionSource = new TaskCompletionSource<object>();
HandleCreated += ToastForm_HandleCreated;
_screenWidth = Screen.PrimaryScreen.WorkingArea.Width;
_screenHeight = Screen.PrimaryScreen.WorkingArea.Height;
}
/// <summary>
/// Display a toast with a title, body and a logo indefinitely on screen.
/// </summary>
/// <param name="title">the toast title</param>
/// <param name="body">the toast body</param>
public ToastForm(string title, string body) : this()
{
_title = title;
_body = body;
var httpClientHandler = new HttpClientHandler
{
// mono does not implement this
//SslProtocols = SslProtocols.Tls12
};
if (Proxy != null && Proxy.Enable)
{
httpClientHandler.Proxy = new WebProxy(Proxy.Url, false, new string[] { },
new NetworkCredential(Proxy.Username, Proxy.Password));
}
_httpClient = new HttpClient(httpClientHandler);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
HandleCreated -= ToastForm_HandleCreated;
if (_toastTimer != null)
{
_toastTimer.Dispose();
_toastTimer = null;
}
if (_image != null)
{
_image.Dispose();
_image = null;
}
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
#region Event Handlers
private void ToastForm_HandleCreated(object sender, EventArgs e)
{
_handleCreatedTaskCompletionSource.TrySetResult(new { });
}
private void Toast_MouseEnter(object sender, EventArgs e)
{
if (_toastTimer == null)
{
return;
}
_toastTimer.Stop();
}
private void Toast_MouseLeave(object sender, EventArgs e)
{
if (_toastTimer == null)
{
return;
}
_toastTimer.Start();
}
private void panel1_Click(object sender, EventArgs e)
{
try
{
Clipboard.SetText(htmlPanel1.Text);
}
catch
{
Debug.WriteLine("Could not copy to clipboard.");
}
}
private void panel2_Click(object sender, EventArgs e)
{
lock (OpenNotificationsLock)
{
this.InvokeIfRequired(form =>
{
form.Close();
});
}
}
private void Toast_Click(object sender, EventArgs e)
{
lock (_toastDetachedLock)
{
if (!_toastDetached)
{
return;
}
BringToFront();
}
}
private async void ToastForm_Load(object sender, EventArgs e)
{
if (EnableChime)
{
try
{
using (var memoryStream = new MemoryStream(Chime))
{
using (var player = new SoundPlayer(memoryStream))
{
player.Play();
}
}
}
catch
{
Debug.WriteLine("Sound file could not be played.");
}
}
await _handleCreatedTaskCompletionSource.Task;
// if no image is provided, collapse the panel for the extra space
// otherwise display the image
switch (_image == null)
{
case true:
splitContainer1.Panel1Collapsed = true;
break;
default:
imageBox.BackgroundImage = _image;
break;
}
// compute notification height from body
var maxWidth = tableLayoutPanel4.Width;
switch (ContentType)
{
case "text/markdown":
var htmlDocument = new HtmlAgilityPack.HtmlDocument();
if (!string.IsNullOrEmpty(_body))
{
htmlDocument.LoadHtml(_body);
if (htmlDocument.DocumentNode != null && htmlDocument.DocumentNode.Descendants().Any())
{
var imgNodes = htmlDocument.DocumentNode.SelectNodes("//img");
if (imgNodes != null && imgNodes.Any())
{
foreach (var node in imgNodes)
{
node.SetAttributeValue("style", $"max-width: {maxWidth}px");
}
}
}
_body = htmlDocument.DocumentNode.WriteTo();
}
break;
default:
break;
}
using (var image = HtmlRender.RenderToImage(_body, maxWidth, 0, Color.Empty))
{
var height = image.Height + labelTitle.Height;
if (height > Height)
{
Height = height;
}
}
// apply the title and the text
labelTitle.Text = _title;
htmlPanel1.Text = _body;
if (EnablePin)
{
lock (OpenNotificationsLock)
{
this.InvokeIfRequired(form =>
{
// set the location of the toast
form.Location = new Point(PinPoint.X, PinPoint.Y);
});
}
// remove top-most
SetWindowPos(Handle, -2, 0, 0, 0, 0, 0x0001 | 0x0002);
return;
}
lock (OpenNotificationsLock)
{
foreach (var openForm in new List<ToastForm>(OpenNotifications))
{
if (openForm == null || openForm.IsDisposed)
{
OpenNotifications.Remove(openForm);
continue;
}
openForm.InvokeIfRequired(form =>
{
// if the form will end up off-screen, then don't even bother moving it
foreach (Screen screen in Screen.AllScreens)
{
if (!screen.WorkingArea.Contains(new Rectangle(form.Left, form.Top - Height, form.Width, form.Height)))
{
form.Close();
OpenNotifications.Remove(form);
return;
}
}
form.Top -= Height;
});
}
// set the location of the toast
Location = new Point(_screenWidth - Width, _screenHeight - Height);
OpenNotifications.Add(this);
// show the form
//Opacity = 1;
}
// set up the timer when the notification should be removed
_toastTimer = new System.Timers.Timer { Enabled = true, AutoReset = false, Interval = LingerTime };
_toastTimer.Elapsed += ToastTimer_Elapsed;
_toastTimer.Start();
}
private async void HtmlPanel1_ImageLoad(object sender, TheArtOfDev.HtmlRenderer.Core.Entities.HtmlImageLoadEventArgs e)
{
if (!string.IsNullOrEmpty(UserAgent))
{
lock (_httpClientDefaultRequestHeadersLock)
{
if (!_httpClient.DefaultRequestHeaders.Contains("User-Agent"))
{
_httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent);
}
}
}
try
{
using (var response = await _httpClient.GetAsync(e.Src))
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
var image = Image.FromStream(responseStream);
e.Callback(image);
e.Handled = true;
}
}
}
catch
{
Debug.WriteLine("Error downloading image.");
}
}
private void ToastForm_FormClosed(object sender, FormClosedEventArgs e)
{
lock (OpenNotificationsLock)
{
OpenNotifications.Remove(this);
}
}
private void ToastTimer_Elapsed(object sender, EventArgs e)
{
if (IsDisposed)
{
return;
}
lock (_toastDetachedLock)
{
if (_toastDetached)
{
return;
}
}
try
{
_toastTimer.Stop();
lock (OpenNotificationsLock)
{
this.InvokeIfRequired(form =>
{
form.Close();
});
}
}
catch
{
Debug.WriteLine("Error in timer elapsed event.");
}
}
private void labelTitle_MouseDown(object sender, MouseEventArgs e)
{
_mouseDown = true;
_lastLocation = e.Location;
}
private void labelTitle_MouseMove(object sender, MouseEventArgs e)
{
if (!_mouseDown)
{
return;
}
Location = new Point((Location.X - _lastLocation.X) + e.X, (Location.Y - _lastLocation.Y) + e.Y);
Update();
lock (_toastDetachedLock)
{
if (_toastDetached)
{
return;
}
_toastDetached = true;
_toastTimer.Elapsed -= ToastTimer_Elapsed;
_toastTimer.Stop();
_toastTimer.Dispose();
_toastTimer = null;
lock (OpenNotificationsLock)
{
OpenNotifications.Remove(this);
}
// remove top-most
SetWindowPos(Handle, -2, 0, 0, 0, 0, 0x0001 | 0x0002);
}
}
private void labelTitle_MouseUp(object sender, MouseEventArgs e)
{
_mouseDown = false;
}
#endregion
#region Private Methods
#endregion
}
}
Generated by GNU Enscript 1.6.5.90.