dorisoy_HardwareMonitor/LibreHardwareMonitor/UI/SensorNotifyIcon.cs
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Utilities;

namespace LibreHardwareMonitor.UI
{
    public class SensorNotifyIcon : IDisposable
    {
        private readonly UnitManager _unitManager;
        private readonly NotifyIconAdv _notifyIcon;
        private readonly Bitmap _bitmap;
        private readonly Graphics _graphics;
        private Color _color;
        private Color _darkColor;
        private Brush _brush;
        private Brush _darkBrush;
        private readonly Pen _pen;
        private readonly Font _font;
        private readonly Font _smallFont;

        public SensorNotifyIcon(SystemTray sensorSystemTray, ISensor sensor, PersistentSettings settings, UnitManager unitManager)
        {
            _unitManager = unitManager;
            Sensor = sensor;
            _notifyIcon = new NotifyIconAdv();

            Color defaultColor = Color.White;
            if (sensor.SensorType == SensorType.Load || sensor.SensorType == SensorType.Control || sensor.SensorType == SensorType.Level)
                defaultColor = Color.FromArgb(0xff, 0x70, 0x8c, 0xf1);

            Color = settings.GetValue(new Identifier(sensor.Identifier, "traycolor").ToString(), defaultColor);

            _pen = new Pen(Color.FromArgb(96, Color.Black));
            ContextMenuStrip contextMenuStrip = new ContextMenuStrip();
            ToolStripItem hideShowItem = new ToolStripMenuItem("Hide/Show");
            hideShowItem.Click += delegate
            {
                sensorSystemTray.SendHideShowCommand();
            };
            contextMenuStrip.Items.Add(hideShowItem);
            contextMenuStrip.Items.Add(new ToolStripSeparator());
            ToolStripItem removeItem = new ToolStripMenuItem("Remove Sensor");
            removeItem.Click += delegate
            {
                sensorSystemTray.Remove(Sensor);
            };
            contextMenuStrip.Items.Add(removeItem);
            ToolStripItem colorItem = new ToolStripMenuItem("Change Color...");
            colorItem.Click += delegate
            {
                ColorDialog dialog = new ColorDialog { Color = Color };
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    Color = dialog.Color;
                    settings.SetValue(new Identifier(sensor.Identifier,
                      "traycolor").ToString(), Color);
                }
            };
            contextMenuStrip.Items.Add(colorItem);
            contextMenuStrip.Items.Add(new ToolStripSeparator());
            ToolStripItem exitItem = new ToolStripMenuItem("Exit");
            exitItem.Click += delegate
            {
                sensorSystemTray.SendExitCommand();
            };
            contextMenuStrip.Items.Add(exitItem);
            _notifyIcon.ContextMenuStrip = contextMenuStrip;
            _notifyIcon.DoubleClick += delegate
            {
                sensorSystemTray.SendHideShowCommand();
            };

            // get the default dpi to create an icon with the correct size
            float dpiX, dpiY;
            using (Bitmap b = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
            {
                dpiX = b.HorizontalResolution;
                dpiY = b.VerticalResolution;
            }

            // adjust the size of the icon to current dpi (default is 16x16 at 96 dpi)
            int width = (int)Math.Round(16 * dpiX / 96);
            int height = (int)Math.Round(16 * dpiY / 96);

            // make sure it does never get smaller than 16x16
            width = width < 16 ? 16 : width;
            height = height < 16 ? 16 : height;

            // adjust the font size to the icon size
            FontFamily family = SystemFonts.MessageBoxFont.FontFamily;
            float baseSize;
            switch (family.Name)
            {
                case "Segoe UI": baseSize = 12; break;
                case "Tahoma": baseSize = 11; break;
                default: baseSize = 12; break;
            }

            _font = new Font(family, baseSize * width / 16.0f, GraphicsUnit.Pixel);
            _smallFont = new Font(family, 0.75f * baseSize * width / 16.0f, GraphicsUnit.Pixel);

            _bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            _graphics = Graphics.FromImage(_bitmap);
            if (Environment.OSVersion.Version.Major > 5)
            {
                _graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                _graphics.SmoothingMode = SmoothingMode.HighQuality;
            }
        }

        public ISensor Sensor { get; }

        public Color Color
        {
            get { return _color; }
            set
            {
                _color = value;
                _darkColor = Color.FromArgb(255, _color.R / 3, _color.G / 3, _color.B / 3);
                Brush brush = _brush;
                _brush = new SolidBrush(_color);
                brush?.Dispose();
                Brush darkBrush = _darkBrush;
                _darkBrush = new SolidBrush(_darkColor);
                darkBrush?.Dispose();
            }
        }

        public void Dispose()
        {
            Icon icon = _notifyIcon.Icon;
            _notifyIcon.Icon = null;
            icon?.Destroy();
            _notifyIcon.Dispose();

            _brush?.Dispose();
            _darkBrush?.Dispose();
            _pen.Dispose();
            _graphics.Dispose();
            _bitmap.Dispose();
            _font.Dispose();
            _smallFont.Dispose();
        }

        private string GetString()
        {
            if (!Sensor.Value.HasValue)
                return "-";

            switch (Sensor.SensorType)
            {
                case SensorType.Temperature:
                    return _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit ? $"{UnitManager.CelsiusToFahrenheit(Sensor.Value):F0}" : $"{Sensor.Value:F0}";
                case SensorType.TimeSpan:
                    return $"{TimeSpan.FromSeconds(Sensor.Value.Value):g}";
                case SensorType.Clock:
                case SensorType.Fan:
                case SensorType.Flow:
                    return $"{1e-3f * Sensor.Value:F1}";
                case SensorType.Voltage:
                case SensorType.Current:
                case SensorType.SmallData:
                case SensorType.Factor:
                case SensorType.Throughput:
                    return $"{Sensor.Value:F1}";
                case SensorType.Control:
                case SensorType.Frequency:
                case SensorType.Level:
                case SensorType.Power:
                case SensorType.Data:
                case SensorType.Load:
                    return $"{Sensor.Value:F0}";
                default:
                    return "-";
            }
        }

        private Icon CreateTransparentIcon()
        {
            string text = GetString();
            int count = 0;
            for (int i = 0; i < text.Length; i++)
                if ((text[i] >= '0' && text[i] <= '9') || text[i] == '-')
                    count++;
            bool small = count > 2;

            _graphics.Clear(Color.Transparent);
            Rectangle bounds = new Rectangle(Point.Empty, _bitmap.Size);
            TextRenderer.DrawText(_graphics, text, small ? _smallFont : _font, bounds, _color, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);

            return IconFactory.Create(_bitmap);
        }

        private Icon CreatePercentageIcon()
        {
            try
            {
                _graphics.Clear(Color.Transparent);
            }
            catch (ArgumentException)
            {
                _graphics.Clear(Color.Black);
            }
            _graphics.FillRectangle(_darkBrush, 0.5f, -0.5f, _bitmap.Width - 2, _bitmap.Height);
            float value = Sensor.Value.GetValueOrDefault();
            float y = 0.16f * (100 - value);
            _graphics.FillRectangle(_brush, 0.5f, -0.5f + y, _bitmap.Width - 2, _bitmap.Height - y);
            _graphics.DrawRectangle(_pen, 1, 0, _bitmap.Width - 3, _bitmap.Height - 1);

            return IconFactory.Create(_bitmap);
        }

        public void Update()
        {
            Icon icon = _notifyIcon.Icon;

            switch (Sensor.SensorType)
            {
                case SensorType.Load:
                case SensorType.Control:
                case SensorType.Level:
                    _notifyIcon.Icon = CreatePercentageIcon();
                    break;
                default:
                    _notifyIcon.Icon = CreateTransparentIcon();
                    break;
            }

            icon?.Destroy();

            string format = "";
            switch (Sensor.SensorType)
            {
                case SensorType.Voltage: format = "\n{0}: {1:F2} V"; break;
                case SensorType.Current: format = "\n{0}: {1:F2} A"; break;
                case SensorType.Clock: format = "\n{0}: {1:F0} MHz"; break;
                case SensorType.Load: format = "\n{0}: {1:F1} %"; break;
                case SensorType.Temperature: format = "\n{0}: {1:F1} °C"; break;
                case SensorType.Fan: format = "\n{0}: {1:F0} RPM"; break;
                case SensorType.Flow: format = "\n{0}: {1:F0} L/h"; break;
                case SensorType.Control: format = "\n{0}: {1:F1} %"; break;
                case SensorType.Level: format = "\n{0}: {1:F1} %"; break;
                case SensorType.Power: format = "\n{0}: {1:F0} W"; break;
                case SensorType.Data: format = "\n{0}: {1:F0} GB"; break;
                case SensorType.Factor: format = "\n{0}: {1:F3} GB"; break;
            }
            string formattedValue = string.Format(format, Sensor.Name, Sensor.Value);

            if (Sensor.SensorType == SensorType.Temperature && _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit)
            {
                format = "\n{0}: {1:F1} °F";
                formattedValue = string.Format(format, Sensor.Name, UnitManager.CelsiusToFahrenheit(Sensor.Value));
            }

            string hardwareName = Sensor.Hardware.Name;
            hardwareName = hardwareName.Substring(0, Math.Min(63 - formattedValue.Length, hardwareName.Length));
            string text = hardwareName + formattedValue;
            if (text.Length > 63)
                text = null;

            _notifyIcon.Text = text;
            _notifyIcon.Visible = true;
        }
    }
}
