GUI/SensorGadget.cs
author StephaneLenclud
Thu, 18 Apr 2013 23:25:10 +0200
branchMiniDisplay
changeset 444 9b09e2ee0968
parent 344 3145aadca3d2
permissions -rw-r--r--
Front View plug-in does not init if no sensor added.
Fixing some format to make strings shorter.
Now trying to start SoundGraphAccess.exe process from same directory.
Packed mode now can display three sensors along with the current time.
     1 /*
     2  
     3   This Source Code Form is subject to the terms of the Mozilla Public
     4   License, v. 2.0. If a copy of the MPL was not distributed with this
     5   file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  
     7   Copyright (C) 2010-2012 Michael Möller <mmoeller@openhardwaremonitor.org>
     8 	
     9 */
    10 
    11 using System;
    12 using System.Collections.Generic;
    13 using System.Drawing;
    14 using System.Drawing.Imaging;
    15 using System.Windows.Forms;
    16 using System.IO;
    17 using OpenHardwareMonitor.Hardware;
    18 
    19 namespace OpenHardwareMonitor.GUI {
    20   public class SensorGadget : Gadget {
    21 
    22     private UnitManager unitManager;
    23 
    24     private Image back = Utilities.EmbeddedResources.GetImage("gadget.png");
    25     private Image image = null;
    26     private Image fore = null;
    27     private Image barBack = Utilities.EmbeddedResources.GetImage("barback.png");
    28     private Image barFore = Utilities.EmbeddedResources.GetImage("barblue.png");
    29     private const int topBorder = 6;
    30     private const int bottomBorder = 7;
    31     private const int leftBorder = 6;
    32     private const int rightBorder = 7;
    33     private Image background = new Bitmap(1, 1);
    34 
    35     private readonly float scale;
    36     private float fontSize;
    37     private int iconSize;
    38     private int hardwareLineHeight;
    39     private int sensorLineHeight;
    40     private int rightMargin;
    41     private int leftMargin;
    42     private int topMargin;
    43     private int bottomMargin;
    44     private int progressWidth;
    45 
    46     private IDictionary<IHardware, IList<ISensor>> sensors =
    47       new SortedDictionary<IHardware, IList<ISensor>>(new HardwareComparer());
    48 
    49     private PersistentSettings settings;
    50     private UserOption hardwareNames;
    51     private UserOption alwaysOnTop;
    52     private UserOption lockPositionAndSize;
    53 
    54     private Font largeFont;
    55     private Font smallFont;
    56     private Brush darkWhite;
    57     private StringFormat stringFormat;
    58     private StringFormat trimStringFormat;
    59     private StringFormat alignRightStringFormat;
    60 
    61     public SensorGadget(IComputer computer, PersistentSettings settings, 
    62       UnitManager unitManager) 
    63     {
    64       this.unitManager = unitManager;
    65       this.settings = settings;
    66       computer.HardwareAdded += new HardwareEventHandler(HardwareAdded);
    67       computer.HardwareRemoved += new HardwareEventHandler(HardwareRemoved);      
    68 
    69       this.darkWhite = new SolidBrush(Color.FromArgb(0xF0, 0xF0, 0xF0));
    70 
    71       this.stringFormat = new StringFormat();
    72       this.stringFormat.FormatFlags = StringFormatFlags.NoWrap;
    73 
    74       this.trimStringFormat = new StringFormat();
    75       this.trimStringFormat.Trimming = StringTrimming.EllipsisCharacter;
    76       this.trimStringFormat.FormatFlags = StringFormatFlags.NoWrap;
    77 
    78       this.alignRightStringFormat = new StringFormat();
    79       this.alignRightStringFormat.Alignment = StringAlignment.Far;
    80       this.alignRightStringFormat.FormatFlags = StringFormatFlags.NoWrap;
    81 
    82       if (File.Exists("gadget_background.png")) {
    83         try { 
    84           Image newBack = new Bitmap("gadget_background.png");
    85           back.Dispose();
    86           back = newBack;
    87         } catch { }
    88       }
    89 
    90       if (File.Exists("gadget_image.png")) {
    91         try {
    92           image = new Bitmap("gadget_image.png"); 
    93         } catch {}
    94       }
    95 
    96       if (File.Exists("gadget_foreground.png")) {
    97         try {
    98           fore = new Bitmap("gadget_foreground.png"); 
    99         } catch { }
   100       }
   101 
   102       if (File.Exists("gadget_bar_background.png")) {
   103         try {
   104           Image newBarBack = new Bitmap("gadget_bar_background.png");
   105           barBack.Dispose();
   106           barBack = newBarBack;
   107         } catch { }
   108       }
   109 
   110       if (File.Exists("gadget_bar_foreground.png")) {
   111         try {
   112           Image newBarColor = new Bitmap("gadget_bar_foreground.png");
   113           barFore.Dispose();
   114           barFore = newBarColor;
   115         } catch { }
   116       }
   117 
   118       this.Location = new Point(
   119         settings.GetValue("sensorGadget.Location.X", 100),
   120         settings.GetValue("sensorGadget.Location.Y", 100)); 
   121       LocationChanged += delegate(object sender, EventArgs e) {
   122         settings.SetValue("sensorGadget.Location.X", Location.X);
   123         settings.SetValue("sensorGadget.Location.Y", Location.Y);
   124       };
   125 
   126       // get the custom to default dpi ratio
   127       using (Bitmap b = new Bitmap(1, 1)) {
   128         scale = b.HorizontalResolution / 96.0f;
   129       }
   130 
   131       SetFontSize(settings.GetValue("sensorGadget.FontSize", 7.5f));
   132       Resize(settings.GetValue("sensorGadget.Width", Size.Width));
   133       
   134       ContextMenu contextMenu = new ContextMenu();
   135       MenuItem hardwareNamesItem = new MenuItem("Hardware Names");
   136       contextMenu.MenuItems.Add(hardwareNamesItem);
   137       MenuItem fontSizeMenu = new MenuItem("Font Size");
   138       for (int i = 0; i < 4; i++) {
   139         float size;
   140         string name;
   141         switch (i) {
   142           case 0: size = 6.5f; name = "Small"; break;
   143           case 1: size = 7.5f; name = "Medium"; break;
   144           case 2: size = 9f; name = "Large"; break;
   145           case 3: size = 11f; name = "Very Large"; break;
   146           default: throw new NotImplementedException();
   147         }
   148         MenuItem item = new MenuItem(name);
   149         item.Checked = fontSize == size;
   150         item.Click += delegate(object sender, EventArgs e) {
   151           SetFontSize(size);
   152           settings.SetValue("sensorGadget.FontSize", size);
   153           foreach (MenuItem mi in fontSizeMenu.MenuItems)
   154             mi.Checked = mi == item;
   155         };
   156         fontSizeMenu.MenuItems.Add(item);
   157       }
   158       contextMenu.MenuItems.Add(fontSizeMenu);
   159       contextMenu.MenuItems.Add(new MenuItem("-"));
   160       MenuItem lockItem = new MenuItem("Lock Position and Size");
   161       contextMenu.MenuItems.Add(lockItem);
   162       contextMenu.MenuItems.Add(new MenuItem("-"));
   163       MenuItem alwaysOnTopItem = new MenuItem("Always on Top");
   164       contextMenu.MenuItems.Add(alwaysOnTopItem);
   165       MenuItem opacityMenu = new MenuItem("Opacity");
   166       contextMenu.MenuItems.Add(opacityMenu);
   167       Opacity = (byte)settings.GetValue("sensorGadget.Opacity", 255);      
   168       for (int i = 0; i < 5; i++) {
   169         MenuItem item = new MenuItem((20 * (i + 1)).ToString() + " %");
   170         byte o = (byte)(51 * (i + 1));
   171         item.Checked = Opacity == o;
   172         item.Click += delegate(object sender, EventArgs e) {
   173           Opacity = o;
   174           settings.SetValue("sensorGadget.Opacity", Opacity);
   175           foreach (MenuItem mi in opacityMenu.MenuItems)
   176             mi.Checked = mi == item;          
   177         };
   178         opacityMenu.MenuItems.Add(item);
   179       }
   180       this.ContextMenu = contextMenu;
   181 
   182       hardwareNames = new UserOption("sensorGadget.Hardwarenames", true,
   183         hardwareNamesItem, settings);
   184       hardwareNames.Changed += delegate(object sender, EventArgs e) {
   185         Resize();
   186       };
   187 
   188       alwaysOnTop = new UserOption("sensorGadget.AlwaysOnTop", false, 
   189         alwaysOnTopItem, settings);
   190       alwaysOnTop.Changed += delegate(object sender, EventArgs e) {
   191         this.AlwaysOnTop = alwaysOnTop.Value;
   192       };
   193       lockPositionAndSize = new UserOption("sensorGadget.LockPositionAndSize", 
   194         false, lockItem, settings);
   195       lockPositionAndSize.Changed += delegate(object sender, EventArgs e) {
   196         this.LockPositionAndSize = lockPositionAndSize.Value;
   197       };
   198 
   199       HitTest += delegate(object sender, HitTestEventArgs e) {
   200         if (lockPositionAndSize.Value)
   201           return;
   202 
   203         if (e.Location.X < leftBorder) {
   204           e.HitResult = HitResult.Left;
   205           return;
   206         }
   207         if (e.Location.X > Size.Width - 1 - rightBorder) {
   208           e.HitResult = HitResult.Right;
   209           return;
   210         }
   211       };
   212 
   213       SizeChanged += delegate(object sender, EventArgs e) {
   214         settings.SetValue("sensorGadget.Width", Size.Width);
   215         Redraw();
   216       };
   217 
   218       VisibleChanged += delegate(object sender, EventArgs e) {
   219         Rectangle bounds = new Rectangle(Location, Size);
   220         Screen screen = Screen.FromRectangle(bounds);
   221         Rectangle intersection = 
   222           Rectangle.Intersect(screen.WorkingArea, bounds);
   223         if (intersection.Width < Math.Min(16, bounds.Width) || 
   224             intersection.Height < Math.Min(16, bounds.Height)) 
   225         {
   226           Location = new Point(
   227             screen.WorkingArea.Width / 2 - bounds.Width / 2, 
   228             screen.WorkingArea.Height / 2 - bounds.Height / 2);
   229         }
   230       };
   231 
   232       MouseDoubleClick += delegate(object obj, MouseEventArgs args) {
   233         SendHideShowCommand();
   234       };
   235     }
   236 
   237     public override void Dispose() {
   238 
   239       largeFont.Dispose();
   240       largeFont = null;
   241 
   242       smallFont.Dispose();
   243       smallFont = null;
   244 
   245       darkWhite.Dispose();
   246       darkWhite = null;
   247 
   248       stringFormat.Dispose();
   249       stringFormat = null;
   250 
   251       trimStringFormat.Dispose();
   252       trimStringFormat = null;
   253 
   254       alignRightStringFormat.Dispose();
   255       alignRightStringFormat = null;     
   256  
   257       back.Dispose();
   258       back = null;
   259 
   260       barFore.Dispose();
   261       barFore = null;
   262 
   263       barBack.Dispose();
   264       barBack = null;
   265 
   266       background.Dispose();
   267       background = null;
   268 
   269       if (image != null) {
   270         image.Dispose();
   271         image = null;
   272       }
   273 
   274       if (fore != null) {
   275         fore.Dispose();
   276         fore = null;
   277       }
   278 
   279       base.Dispose();
   280     }
   281 
   282     private void HardwareRemoved(IHardware hardware) {
   283       hardware.SensorAdded -= new SensorEventHandler(SensorAdded);
   284       hardware.SensorRemoved -= new SensorEventHandler(SensorRemoved);
   285       foreach (ISensor sensor in hardware.Sensors)
   286         SensorRemoved(sensor);
   287       foreach (IHardware subHardware in hardware.SubHardware)
   288         HardwareRemoved(subHardware);
   289     }
   290 
   291     private void HardwareAdded(IHardware hardware) {
   292       foreach (ISensor sensor in hardware.Sensors)
   293         SensorAdded(sensor);
   294       hardware.SensorAdded += new SensorEventHandler(SensorAdded);
   295       hardware.SensorRemoved += new SensorEventHandler(SensorRemoved);
   296       foreach (IHardware subHardware in hardware.SubHardware)
   297         HardwareAdded(subHardware);
   298     }
   299 
   300     private void SensorAdded(ISensor sensor) {
   301       if (settings.GetValue(new Identifier(sensor.Identifier,
   302         "gadget").ToString(), false)) 
   303         Add(sensor);
   304     }
   305 
   306     private void SensorRemoved(ISensor sensor) {
   307       if (Contains(sensor))
   308         Remove(sensor, false);
   309     }
   310 
   311     public bool Contains(ISensor sensor) {
   312       foreach (IList<ISensor> list in sensors.Values)
   313         if (list.Contains(sensor))
   314           return true;
   315       return false;
   316     }
   317 
   318     public void Add(ISensor sensor) {
   319       if (Contains(sensor)) {
   320         return;
   321       } else {
   322         // get the right hardware
   323         IHardware hardware = sensor.Hardware;
   324         while (hardware.Parent != null)
   325           hardware = hardware.Parent;
   326 
   327         // get the sensor list associated with the hardware
   328         IList<ISensor> list;
   329         if (!sensors.TryGetValue(hardware, out list)) {
   330           list = new List<ISensor>();
   331           sensors.Add(hardware, list);
   332         }
   333 
   334         // insert the sensor at the right position
   335         int i = 0;
   336         while (i < list.Count && (list[i].SensorType < sensor.SensorType || 
   337           (list[i].SensorType == sensor.SensorType && 
   338            list[i].Index < sensor.Index))) i++;
   339         list.Insert(i, sensor);
   340 
   341         settings.SetValue(
   342           new Identifier(sensor.Identifier, "gadget").ToString(), true);
   343         
   344         Resize();
   345       }
   346     }
   347 
   348     public void Remove(ISensor sensor) {
   349       Remove(sensor, true);
   350     }
   351 
   352     private void Remove(ISensor sensor, bool deleteConfig) {
   353       if (deleteConfig) 
   354         settings.Remove(new Identifier(sensor.Identifier, "gadget").ToString());
   355 
   356       foreach (KeyValuePair<IHardware, IList<ISensor>> keyValue in sensors)
   357         if (keyValue.Value.Contains(sensor)) {
   358           keyValue.Value.Remove(sensor);          
   359           if (keyValue.Value.Count == 0) {
   360             sensors.Remove(keyValue.Key);
   361             break;
   362           }
   363         }
   364       Resize();
   365     }
   366 
   367     public event EventHandler HideShowCommand;
   368 
   369     public void SendHideShowCommand() {
   370       if (HideShowCommand != null)
   371         HideShowCommand(this, null);
   372     }
   373 
   374     private Font CreateFont(float size, FontStyle style) {
   375       try {
   376         return new Font(SystemFonts.MessageBoxFont.FontFamily, size, style);
   377       } catch (ArgumentException) {
   378         // if the style is not supported, fall back to the original one
   379         return new Font(SystemFonts.MessageBoxFont.FontFamily, size, 
   380           SystemFonts.MessageBoxFont.Style);
   381       }
   382     }
   383 
   384     private void SetFontSize(float size) {
   385       fontSize = size;
   386       largeFont = CreateFont(fontSize, FontStyle.Bold);
   387       smallFont = CreateFont(fontSize, FontStyle.Regular);
   388       
   389       double scaledFontSize = fontSize * scale;
   390       iconSize = (int)Math.Round(1.5 * scaledFontSize);
   391       hardwareLineHeight = (int)Math.Round(1.66 * scaledFontSize);
   392       sensorLineHeight = (int)Math.Round(1.33 * scaledFontSize);
   393       leftMargin = leftBorder + (int)Math.Round(0.3 * scaledFontSize);
   394       rightMargin = rightBorder + (int)Math.Round(0.3 * scaledFontSize);
   395       topMargin = topBorder;
   396       bottomMargin = bottomBorder + (int)Math.Round(0.3 * scaledFontSize);
   397       progressWidth = (int)Math.Round(5.3 * scaledFontSize);
   398 
   399       Resize((int)Math.Round(17.3 * scaledFontSize));
   400     }
   401 
   402     private void Resize() {
   403       Resize(this.Size.Width);
   404     }
   405 
   406     private void Resize(int width) {
   407       int y = topMargin;      
   408       foreach (KeyValuePair<IHardware, IList<ISensor>> pair in sensors) {
   409         if (hardwareNames.Value) {
   410           if (y > topMargin)
   411             y += hardwareLineHeight - sensorLineHeight;
   412           y += hardwareLineHeight;
   413         }
   414         y += pair.Value.Count * sensorLineHeight;
   415       }      
   416       if (sensors.Count == 0)
   417         y += 4 * sensorLineHeight + hardwareLineHeight;
   418       y += bottomMargin;
   419       this.Size = new Size(width, y);
   420     }
   421 
   422     private void DrawImageWidthBorder(Graphics g, int width, int height, 
   423       Image back, int t, int b, int l, int r) 
   424     {
   425       GraphicsUnit u = GraphicsUnit.Pixel;
   426 
   427       g.DrawImage(back, new Rectangle(0, 0, l, t),
   428             new Rectangle(0, 0, l, t), u);
   429       g.DrawImage(back, new Rectangle(l, 0, width - l - r, t),
   430         new Rectangle(l, 0, back.Width - l - r, t), u);
   431       g.DrawImage(back, new Rectangle(width - r, 0, r, t),
   432         new Rectangle(back.Width - r, 0, r, t), u);
   433 
   434       g.DrawImage(back, new Rectangle(0, t, l, height - t - b),
   435         new Rectangle(0, t, l, back.Height - t - b), u);
   436       g.DrawImage(back, new Rectangle(l, t, width - l - r, height - t - b),
   437         new Rectangle(l, t, back.Width - l - r, back.Height - t - b), u);
   438       g.DrawImage(back, new Rectangle(width - r, t, r, height - t - b),
   439         new Rectangle(back.Width - r, t, r, back.Height - t - b), u);
   440 
   441       g.DrawImage(back, new Rectangle(0, height - b, l, b),
   442         new Rectangle(0, back.Height - b, l, b), u);
   443       g.DrawImage(back, new Rectangle(l, height - b, width - l - r, b),
   444         new Rectangle(l, back.Height - b, back.Width - l - r, b), u);
   445       g.DrawImage(back, new Rectangle(width - r, height - b, r, b),
   446         new Rectangle(back.Width - r, back.Height - b, r, b), u);
   447     }
   448 
   449     private void DrawBackground(Graphics g) {
   450       int w = Size.Width;
   451       int h = Size.Height;      
   452 
   453       if (w != background.Width || h != background.Height) {
   454 
   455         background.Dispose();
   456         background = new Bitmap(w, h, PixelFormat.Format32bppPArgb);
   457         using (Graphics graphics = Graphics.FromImage(background)) {
   458 
   459           DrawImageWidthBorder(graphics, w, h, back, topBorder, bottomBorder, 
   460             leftBorder, rightBorder);    
   461       
   462           if (fore != null)
   463             DrawImageWidthBorder(graphics, w, h, fore, topBorder, bottomBorder,
   464             leftBorder, rightBorder);
   465 
   466           if (image != null) {
   467             int width = w - leftBorder - rightBorder;
   468             int height = h - topBorder - bottomBorder;
   469             float xRatio = width / (float)image.Width;
   470             float yRatio = height / (float)image.Height;
   471             float destWidth, destHeight;
   472             float xOffset, yOffset;
   473             if (xRatio < yRatio) {
   474               destWidth = width;
   475               destHeight = image.Height * xRatio;
   476               xOffset = 0;
   477               yOffset = 0.5f * (height - destHeight);
   478             } else {
   479               destWidth = image.Width * yRatio;
   480               destHeight = height;
   481               xOffset = 0.5f * (width - destWidth);
   482               yOffset = 0;
   483             }
   484 
   485             graphics.DrawImage(image,
   486               new RectangleF(leftBorder + xOffset, topBorder + yOffset, 
   487                 destWidth, destHeight));
   488           }
   489         }
   490       }
   491 
   492       g.DrawImageUnscaled(background, 0, 0);
   493     }
   494 
   495     private void DrawProgress(Graphics g, float x, float y, 
   496       float width, float height, float progress) 
   497     {
   498       g.DrawImage(barBack, 
   499         new RectangleF(x + width * progress, y, width * (1 - progress), height), 
   500         new RectangleF(barBack.Width * progress, 0, 
   501           (1 - progress) * barBack.Width, barBack.Height), 
   502         GraphicsUnit.Pixel);
   503       g.DrawImage(barFore,
   504         new RectangleF(x, y, width * progress, height),
   505         new RectangleF(0, 0, progress * barFore.Width, barFore.Height),
   506         GraphicsUnit.Pixel);
   507     }
   508 
   509     protected override void OnPaint(PaintEventArgs e) {
   510       Graphics g = e.Graphics;
   511       int w = Size.Width;
   512 
   513       g.Clear(Color.Transparent);
   514       
   515       DrawBackground(g);
   516 
   517       int x;
   518       int y = topMargin;
   519 
   520       if (sensors.Count == 0) {
   521         x = leftBorder + 1;
   522         g.DrawString("Right-click on a sensor in the main window and select " + 
   523           "\"Show in Gadget\" to show the sensor here.", 
   524           smallFont, Brushes.White,
   525           new Rectangle(x, y - 1, w - rightBorder - x, 0));
   526       }
   527 
   528       foreach (KeyValuePair<IHardware, IList<ISensor>> pair in sensors) {
   529         if (hardwareNames.Value) {
   530           if (y > topMargin)
   531             y += hardwareLineHeight - sensorLineHeight;
   532           x = leftBorder + 1;
   533           g.DrawImage(HardwareTypeImage.Instance.GetImage(pair.Key.HardwareType),
   534             new Rectangle(x, y + 1, iconSize, iconSize));
   535           x += iconSize + 1;
   536           g.DrawString(pair.Key.Name, largeFont, Brushes.White,
   537             new Rectangle(x, y - 1, w - rightBorder - x, 0), 
   538             stringFormat);
   539           y += hardwareLineHeight;
   540         }
   541 
   542         foreach (ISensor sensor in pair.Value) {
   543           int remainingWidth;
   544 
   545 
   546           if ((sensor.SensorType != SensorType.Load &&
   547                sensor.SensorType != SensorType.Control &&
   548                sensor.SensorType != SensorType.Level) || !sensor.Value.HasValue) 
   549           {
   550             string formatted;
   551 
   552             if (sensor.Value.HasValue) {
   553               string format = "";
   554               switch (sensor.SensorType) {
   555                 case SensorType.Voltage:
   556                   format = "{0:F3} V";
   557                   break;
   558                 case SensorType.Clock:
   559                   format = "{0:F0} MHz";
   560                   break;
   561                 case SensorType.Temperature:
   562                   format = "{0:F1} °C";
   563                   break;
   564                 case SensorType.Fan:
   565                   format = "{0:F0} RPM";
   566                   break;
   567                 case SensorType.Flow:
   568                   format = "{0:F0} L/h";
   569                   break;
   570                 case SensorType.Power:
   571                   format = "{0:F1} W";
   572                   break;
   573                 case SensorType.Data:
   574                   format = "{0:F1} GB";
   575                   break;
   576                 case SensorType.Factor:
   577                   format = "{0:F3}";
   578                   break;
   579               }
   580 
   581               if (sensor.SensorType == SensorType.Temperature &&
   582                 unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) {
   583                 formatted = string.Format("{0:F1} °F",
   584                   UnitManager.CelsiusToFahrenheit(sensor.Value));
   585               } else {
   586                 formatted = string.Format(format, sensor.Value);
   587               }
   588             } else {
   589               formatted = "-";
   590             }
   591 
   592             g.DrawString(formatted, smallFont, darkWhite,
   593               new RectangleF(-1, y - 1, w - rightMargin + 3, 0),
   594               alignRightStringFormat);
   595 
   596             remainingWidth = w - (int)Math.Floor(g.MeasureString(formatted,
   597               smallFont, w, StringFormat.GenericTypographic).Width) -
   598               rightMargin;
   599           } else {
   600             DrawProgress(g, w - progressWidth - rightMargin,
   601               y + 0.35f * sensorLineHeight, progressWidth,
   602               0.6f * sensorLineHeight, 0.01f * sensor.Value.Value);
   603 
   604             remainingWidth = w - progressWidth - rightMargin;
   605           }
   606            
   607           remainingWidth -= leftMargin + 2;
   608           if (remainingWidth > 0) {
   609             g.DrawString(sensor.Name, smallFont, darkWhite,
   610               new RectangleF(leftMargin - 1, y - 1, remainingWidth, 0), 
   611               trimStringFormat);
   612           }
   613 
   614           y += sensorLineHeight;
   615         }
   616       }
   617     }
   618 
   619     private class HardwareComparer : IComparer<IHardware> {
   620       public int Compare(IHardware x, IHardware y) {
   621         if (x == null && y == null)
   622           return 0;
   623         if (x == null)
   624           return -1;
   625         if (y == null)
   626           return 1;
   627 
   628         if (x.HardwareType != y.HardwareType)
   629           return x.HardwareType.CompareTo(y.HardwareType);
   630 
   631         return x.Identifier.CompareTo(y.Identifier);
   632       }
   633     }
   634   }
   635 }
   636