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 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: }