moel@202: /*
moel@40:  
moel@344:   This Source Code Form is subject to the terms of the Mozilla Public
moel@344:   License, v. 2.0. If a copy of the MPL was not distributed with this
moel@344:   file, You can obtain one at http://mozilla.org/MPL/2.0/.
moel@40:  
moel@344:   Copyright (C) 2009-2012 Michael Möller <mmoeller@openhardwaremonitor.org>
moel@344: 	
moel@40: */
moel@40: 
moel@40: using System;
moel@40: using System.Drawing;
moel@40: using System.Drawing.Drawing2D;
moel@40: using System.Drawing.Imaging;
moel@40: using System.Drawing.Text;
moel@176: using System.Runtime.InteropServices;
moel@40: using System.Windows.Forms;
moel@40: using OpenHardwareMonitor.Hardware;
moel@40: using OpenHardwareMonitor.Utilities;
moel@40: 
moel@40: namespace OpenHardwareMonitor.GUI {
moel@40:   public class SensorNotifyIcon : IDisposable {
moel@40: 
moel@362:     private UnitManager unitManager;
moel@362: 
moel@40:     private ISensor sensor;
moel@363:     private NotifyIconAdv notifyIcon;
moel@40:     private Bitmap bitmap;
moel@40:     private Graphics graphics;
moel@42:     private Color color;
moel@43:     private Color darkColor;
moel@43:     private Brush brush;
moel@43:     private Brush darkBrush;
moel@43:     private Pen pen;
moel@70:     private Font font;
moel@362:     private Font smallFont;
moel@40: 
moel@133:     public SensorNotifyIcon(SystemTray sensorSystemTray, ISensor sensor,
moel@362:       bool balloonTip, PersistentSettings settings, UnitManager unitManager) 
moel@42:     {
moel@362:       this.unitManager = unitManager;
moel@40:       this.sensor = sensor;
moel@363:       this.notifyIcon = new NotifyIconAdv();
moel@43: 
moel@43:       Color defaultColor = Color.Black;
moel@217:       if (sensor.SensorType == SensorType.Load ||
moel@217:           sensor.SensorType == SensorType.Control ||
moel@217:           sensor.SensorType == SensorType.Level) 
moel@217:       {
moel@43:         defaultColor = Color.FromArgb(0xff, 0x70, 0x8c, 0xf1);
moel@43:       }
moel@166:       Color = settings.GetValue(new Identifier(sensor.Identifier, 
moel@109:         "traycolor").ToString(), defaultColor);      
moel@40:       
moel@43:       this.pen = new Pen(Color.FromArgb(96, Color.Black));
moel@43: 
moel@156:       ContextMenu contextMenu = new ContextMenu();
moel@156:       MenuItem hideShowItem = new MenuItem("Hide/Show");
moel@133:       hideShowItem.Click += delegate(object obj, EventArgs args) {
moel@133:         sensorSystemTray.SendHideShowCommand();
moel@133:       };
moel@156:       contextMenu.MenuItems.Add(hideShowItem);
moel@156:       contextMenu.MenuItems.Add(new MenuItem("-"));
moel@156:       MenuItem removeItem = new MenuItem("Remove Sensor");
moel@42:       removeItem.Click += delegate(object obj, EventArgs args) {
moel@42:         sensorSystemTray.Remove(this.sensor);
moel@40:       };
moel@156:       contextMenu.MenuItems.Add(removeItem);
moel@156:       MenuItem colorItem = new MenuItem("Change Color...");
moel@42:       colorItem.Click += delegate(object obj, EventArgs args) {
moel@42:         ColorDialog dialog = new ColorDialog();
moel@43:         dialog.Color = Color;
moel@42:         if (dialog.ShowDialog() == DialogResult.OK) {
moel@43:           Color = dialog.Color;
moel@166:           settings.SetValue(new Identifier(sensor.Identifier,
moel@109:             "traycolor").ToString(), Color);
moel@42:         }
moel@42:       };
moel@156:       contextMenu.MenuItems.Add(colorItem);
moel@156:       contextMenu.MenuItems.Add(new MenuItem("-"));
moel@156:       MenuItem exitItem = new MenuItem("Exit");
moel@133:       exitItem.Click += delegate(object obj, EventArgs args) {
moel@133:         sensorSystemTray.SendExitCommand();
moel@133:       };
moel@156:       contextMenu.MenuItems.Add(exitItem);
moel@156:       this.notifyIcon.ContextMenu = contextMenu;
moel@133:       this.notifyIcon.DoubleClick += delegate(object obj, EventArgs args) {
moel@133:         sensorSystemTray.SendHideShowCommand();
moel@366:       };      
moel@40: 
moel@252:       // get the default dpi to create an icon with the correct size
moel@252:       float dpiX, dpiY;
moel@252:       using (Bitmap b = new Bitmap(1, 1, PixelFormat.Format32bppArgb)) {
moel@252:         dpiX = b.HorizontalResolution;
moel@252:         dpiY = b.VerticalResolution;
moel@252:       }
moel@252: 
moel@252:       // adjust the size of the icon to current dpi (default is 16x16 at 96 dpi) 
moel@252:       int width = (int)Math.Round(16 * dpiX / 96);
moel@252:       int height = (int)Math.Round(16 * dpiY / 96);
moel@252: 
moel@252:       // make sure it does never get smaller than 16x16
moel@366:       width = width < 16 ? 16 : width;
moel@366:       height = height < 16 ? 16 : height;
moel@366: 
moel@366:       // adjust the font size to the icon size
moel@366:       FontFamily family = SystemFonts.MessageBoxFont.FontFamily;
moel@366:       float baseSize;
moel@366:       switch (family.Name) {
moel@366:         case "Segoe UI": baseSize = 12; break;
moel@366:         case "Tahoma": baseSize = 11; break;
moel@366:         default: baseSize = 12; break;
moel@366:       }
moel@366: 
moel@366:       this.font = new Font(family,
moel@366:         baseSize * width / 16.0f, GraphicsUnit.Pixel);
moel@366:       this.smallFont = new Font(family, 
moel@366:         0.75f * baseSize * width / 16.0f, GraphicsUnit.Pixel);
moel@252: 
moel@252:       this.bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);      
moel@40:       this.graphics = Graphics.FromImage(this.bitmap);
moel@117: 
moel@117:       if (Environment.OSVersion.Version.Major > 5) {
moel@117:         this.graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
moel@117:         this.graphics.SmoothingMode = SmoothingMode.HighQuality;
moel@117:       }
moel@40:     }
moel@40: 
moel@40:     public ISensor Sensor {
moel@40:       get { return sensor; }
moel@40:     }
moel@40: 
moel@42:     public Color Color {
moel@42:       get { return color; }
moel@43:       set { 
moel@43:         this.color = value;
moel@43:         this.darkColor = Color.FromArgb(255,
moel@44:           this.color.R / 3,
moel@44:           this.color.G / 3,
moel@44:           this.color.B / 3);
moel@43:         Brush brush = this.brush;
moel@43:         this.brush = new SolidBrush(this.color);
moel@43:         if (brush != null)
moel@43:           brush.Dispose();
moel@43:         Brush darkBrush = this.darkBrush;
moel@43:         this.darkBrush = new SolidBrush(this.darkColor);
moel@43:         if (darkBrush != null)
moel@43:           darkBrush.Dispose();
moel@43:       }
moel@42:     }
moel@42: 
moel@40:     public void Dispose() {      
moel@40:       Icon icon = notifyIcon.Icon;
moel@40:       notifyIcon.Icon = null;
moel@40:       if (icon != null)
moel@40:         icon.Dispose();      
moel@40:       notifyIcon.Dispose();
moel@40: 
moel@43:       if (brush != null)
moel@43:         brush.Dispose();
moel@43:       if (darkBrush != null)
moel@43:         darkBrush.Dispose();
moel@43:       pen.Dispose();
moel@85:       graphics.Dispose();      
moel@362:       bitmap.Dispose();
moel@366:       font.Dispose();
moel@362:       smallFont.Dispose();
moel@40:     }
moel@40: 
moel@40:     private string GetString() {
moel@282:       if (!sensor.Value.HasValue)
moel@282:         return "-";
moel@282: 
moel@40:       switch (sensor.SensorType) {
moel@40:         case SensorType.Voltage:
moel@340:           return string.Format("{0:F1}", sensor.Value);
moel@40:         case SensorType.Clock:
moel@340:           return string.Format("{0:F1}", 1e-3f * sensor.Value);
moel@40:         case SensorType.Load: 
moel@118:           return string.Format("{0:F0}", sensor.Value);
moel@362:         case SensorType.Temperature:
moel@362:           if (unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit)
moel@362:             return string.Format("{0:F0}", 
moel@362:               UnitManager.CelsiusToFahrenheit(sensor.Value));
moel@366:           else
moel@362:             return string.Format("{0:F0}", sensor.Value);
moel@40:         case SensorType.Fan: 
moel@340:           return string.Format("{0:F1}", 1e-3f * sensor.Value);
moel@57:         case SensorType.Flow:
moel@340:           return string.Format("{0:F1}", 1e-3f * sensor.Value);
moel@118:         case SensorType.Control:
moel@118:           return string.Format("{0:F0}", sensor.Value);
moel@217:         case SensorType.Level:
moel@217:           return string.Format("{0:F0}", sensor.Value);
moel@317:         case SensorType.Power:
moel@317:           return string.Format("{0:F0}", sensor.Value);
moel@324:         case SensorType.Data:
moel@324:           return string.Format("{0:F0}", sensor.Value);
moel@340:         case SensorType.Factor:
moel@340:           return string.Format("{0:F1}", sensor.Value);
moel@40:       }
moel@40:       return "-";
moel@40:     }
moel@40: 
moel@40:     private Icon CreateTransparentIcon() {
moel@362:       string text = GetString();
moel@362:       int count = 0;
moel@362:       for (int i = 0; i < text.Length; i++)
moel@362:         if ((text[i] >= '0' && text[i] <= '9') || text[i] == '-')
moel@362:           count++;
moel@362:       bool small = count > 2;
moel@40: 
moel@40:       graphics.Clear(Color.Black);
moel@362:       TextRenderer.DrawText(graphics, text, small ? smallFont : font,
moel@362:         new Point(-2, small ? 1 : 0), Color.White, Color.Black);        
moel@40: 
moel@40:       BitmapData data = bitmap.LockBits(
moel@40:         new Rectangle(0, 0, bitmap.Width, bitmap.Height),
moel@40:         ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
moel@40: 
moel@40:       IntPtr Scan0 = data.Scan0;
moel@40: 
moel@40:       int numBytes = bitmap.Width * bitmap.Height * 4;
moel@40:       byte[] bytes = new byte[numBytes];
moel@40:       Marshal.Copy(Scan0, bytes, 0, numBytes);
moel@40:       bitmap.UnlockBits(data);
moel@40: 
moel@40:       byte red, green, blue;
moel@40:       for (int i = 0; i < bytes.Length; i += 4) {
moel@40:         blue = bytes[i];
moel@40:         green = bytes[i + 1];
moel@40:         red = bytes[i + 2];
moel@40: 
moel@42:         bytes[i] = color.B;
moel@42:         bytes[i + 1] = color.G;
moel@42:         bytes[i + 2] = color.R;
moel@40:         bytes[i + 3] = (byte)(0.3 * red + 0.59 * green + 0.11 * blue);
moel@40:       }
moel@40: 
moel@252:       return IconFactory.Create(bytes, bitmap.Width, bitmap.Height, 
moel@252:         PixelFormat.Format32bppArgb);
moel@40:     }
moel@40: 
moel@217:     private Icon CreatePercentageIcon() {      
moel@86:       try {
moel@86:         graphics.Clear(Color.Transparent);
moel@86:       } catch (ArgumentException) {
moel@86:         graphics.Clear(Color.Black);
moel@86:       }
moel@252:       graphics.FillRectangle(darkBrush, 0.5f, -0.5f, bitmap.Width - 2, bitmap.Height);
moel@282:       float value = sensor.Value.GetValueOrDefault();
moel@282:       float y = 0.16f * (100 - value);
moel@252:       graphics.FillRectangle(brush, 0.5f, -0.5f + y, bitmap.Width - 2, bitmap.Height - y);
moel@252:       graphics.DrawRectangle(pen, 1, 0, bitmap.Width - 3, bitmap.Height - 1);
moel@43: 
moel@43:       BitmapData data = bitmap.LockBits(
moel@43:         new Rectangle(0, 0, bitmap.Width, bitmap.Height),
moel@43:         ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
moel@43:       byte[] bytes = new byte[bitmap.Width * bitmap.Height * 4];
moel@43:       Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
moel@43:       bitmap.UnlockBits(data);
moel@43: 
moel@252:       return IconFactory.Create(bytes, bitmap.Width, bitmap.Height, 
moel@252:         PixelFormat.Format32bppArgb);
moel@43:     }
moel@43: 
moel@40:     public void Update() {
moel@40:       Icon icon = notifyIcon.Icon;
moel@40: 
moel@217:       switch (sensor.SensorType) {
moel@217:         case SensorType.Load:
moel@217:         case SensorType.Control:
moel@217:         case SensorType.Level:
moel@217:           notifyIcon.Icon = CreatePercentageIcon();
moel@217:           break;
moel@217:         default:
moel@217:           notifyIcon.Icon = CreateTransparentIcon();
moel@217:           break;
moel@40:       }
moel@217: 
moel@40:       if (icon != null) 
moel@40:         icon.Dispose();
moel@40: 
moel@40:       string format = "";
moel@40:       switch (sensor.SensorType) {
moel@116:         case SensorType.Voltage: format = "\n{0}: {1:F2} V"; break;
moel@116:         case SensorType.Clock: format = "\n{0}: {1:F0} MHz"; break;
moel@116:         case SensorType.Load: format = "\n{0}: {1:F1} %"; break;
moel@116:         case SensorType.Temperature: format = "\n{0}: {1:F1} °C"; break;
moel@116:         case SensorType.Fan: format = "\n{0}: {1:F0} RPM"; break;
moel@116:         case SensorType.Flow: format = "\n{0}: {1:F0} L/h"; break;
moel@118:         case SensorType.Control: format = "\n{0}: {1:F1} %"; break;
moel@217:         case SensorType.Level: format = "\n{0}: {1:F1} %"; break;
moel@317:         case SensorType.Power: format = "\n{0}: {1:F0} W"; break;
moel@324:         case SensorType.Data: format = "\n{0}: {1:F0} GB"; break;
moel@340:         case SensorType.Factor: format = "\n{0}: {1:F3} GB"; break;
moel@40:       }
moel@116:       string formattedValue = string.Format(format, sensor.Name, sensor.Value);
moel@362: 
moel@362:       if (sensor.SensorType == SensorType.Temperature &&
moel@362:         unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) 
moel@362:       {
moel@362:         format = "\n{0}: {1:F1} °F";
moel@362:         formattedValue = string.Format(format, sensor.Name,
moel@362:           UnitManager.CelsiusToFahrenheit(sensor.Value));
moel@362:       }
moel@362: 
moel@116:       string hardwareName = sensor.Hardware.Name;
moel@116:       hardwareName = hardwareName.Substring(0, 
moel@116:         Math.Min(63 - formattedValue.Length, hardwareName.Length));
moel@116:       string text = hardwareName + formattedValue;
moel@116:       if (text.Length > 63)
moel@116:         text = null;
moel@40: 
moel@116:       notifyIcon.Text = text;
moel@42:       notifyIcon.Visible = true;         
moel@40:     }
moel@40:   }
moel@40: }