/*  MeterManager.cs

This file is part of a program that implements a Software-Defined Radio.

This code/file can be found on GitHub : https://github.com/ramdor/Thetis

Copyright (C) 2020-2024 Richard Samphire MW0LGE

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

The author can be reached by email at

mw0lge@grange-lane.co.uk
*/
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System.Xml;
using System.Threading.Tasks;
using System.ComponentModel;
using System.IO.Ports;

//directX
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;

namespace Thetis
{
    // this enum is similar to MeterRXMode & MeterTXMode
    [Serializable]
    public enum Reading
    {
        NONE = -1,
        //RX
        SIGNAL_STRENGTH = 0,
        AVG_SIGNAL_STRENGTH,
        ADC_PK,
        ADC_AV,
        AGC_PK,
        AGC_AV,
        AGC_GAIN,
        ESTIMATED_PBSNR,
        //TX
        MIC,
        PWR,
        ALC,
        EQ,
        LEVELER,
        COMP,
        //CPDR, //CPDR is the same as comp
        ALC_G,
        ALC_GROUP,
        LVL_G,
        MIC_PK,
        ALC_PK,
        EQ_PK,
        LEVELER_PK,
        COMP_PK,
        //CPDR_PK, //CPDR is the same as comp
        CFC_PK = 24,
        CFC_AV,
        CFC_G,

        //additional to MeterRXMode & MeterTXMode
        REVERSE_PWR,
        SWR,

        //pa
        DRIVE_FWD_ADC,
        FWD_ADC,
        REV_ADC,
        DRIVE_PWR,
        PA_FWD_PWR,
        PA_REV_PWR,
        CAL_FWD_PWR,
        REV_VOLT,
        FWD_VOLT,

        // volts/amps
        VOLTS,
        AMPS,

        AZ,
        ELE,

        // special
        //EYE_PERCENT,
        //// private used in metermanager only
        //VFOA,
        //VFOB,
        //VFOSUB,
        //DSPMODE,
        //BANDVFOA,
        //BANDVFOB,
        //SPLIT,
        //TXVFOB,

        //special these are not floats, only used by clsDataOut
        VFOA_FREQ,
        VFOB_FREQ,
        VFOSUBA_FREQ,
        TX_FREQ,
        TIME_UTC,
        DATE_UTC,
        TIME_LOC,
        DATE_LOC,
        VFOA_BAND,
        VFOB_BAND,
        VFOSUBA_BAND,
        VFOA_FILTER_NAME,
        VFOB_FILTER_NAME,
        TX_BAND,
        VFOA_MODE,
        VFOB_MODE,
        SPLIT,
        RX2_ENABLED,
        VFOB_TX,
        SUB_RX,
        //

        LAST
    }

    public enum MeterType
    {
        NONE = 0,
        //rx
        SIGNAL_STRENGTH,
        AVG_SIGNAL_STRENGTH,
        SIGNAL_TEXT,
        ADC,
        AGC,
        AGC_GAIN,
        ESTIMATED_PBSNR,
        //tx
        MIC,
        EQ,
        LEVELER,
        LEVELER_GAIN,
        ALC,
        ALC_GAIN,
        ALC_GROUP,
        CFC,
        CFC_GAIN,
        COMP,
        //--
        PWR,
        REVERSE_PWR,
        SWR,
        //CPDR, //CPDR is the same as comp
        //special
        MAGIC_EYE,
        ANANMM,
        CROSS,
        VFO_DISPLAY,
        CLOCK,
        SPACER,
        TEXT_OVERLAY,
        DATA_OUT,
        ROTATOR,
        LED,
        WEB_IMAGE,
        BAND_BUTTONS,
        MODE_BUTTONS,
        FILTER_BUTTONS,
        ANTENNA_BUTTONS,
        HISTORY,
        TUNESTEP_BUTTONS,
        DISCORD_BUTTONS,
        //SPECTRUM,
        LAST
    }
    public enum BandGroups
    {
        GEN = 0,
        HF,
        VHF,
        LAST = 99
    }
    public class Globals
    {
        public Dictionary<string, object> Variables;
    }
    internal static class MeterManager
    {
        #region MeterManager
        // member variables
        private static Console _console;
        private static bool _delegatesAdded;
        private static bool _finishedSetup;
        //private static bool _power; //not used yet
        private static Dictionary<int, clsReadings> _readings;
        private static Dictionary<string, clsMeter> _meters;
        private static Dictionary<int, bool> _readingIgnore;
        private static Thread _meterThread;
        private static bool _meterThreadRunning;
        //private static object _readingsLock = new object();        
        private static bool _rx1VHForAbove;
        private static bool _rx2VHForAbove;

        private static HPSDRModel _currentHPSDRmodel;
        private static bool _alexPresent;
        private static bool _paPresent;
        private static bool _apolloPresent;
        private static int _transverterIndex;

        private static Object _imageLock = new Object();
        private static Object _metersLock = new Object();

        private static Dictionary<string, MemoryStream> _image_streamdata_cache;
        private static Dictionary<string, System.Drawing.Bitmap> _image_cache;

        private static string _openHPSDR_appdatapath;

        private static string _current_skin = "IK3VIG Special"; // matches selectSkin() fn in setup.cs
        private static string _current_skin_path = "";

        private static CustomReadings[] _custom_readings;

        private static ImageFetcher _image_fetcher;

        private static bool[][] _rx_ant = null;
        private static bool[][] _tx_ant = null;
        private static bool[][] _rx_ant_aux = null;
        private static bool[] _ant_no_tx;
        private static string[] _ant_aux_names;

        private static Dictionary<string, frmMeterDisplay> _lstMeterDisplayForms = new Dictionary<string, frmMeterDisplay>();
        private static Dictionary<string, ucMeter> _lstUCMeters = new Dictionary<string, ucMeter>();

        //public static float[] _newSpectrumPassband;
        //public static float[] _currentSpectrumPassband;
        //private static bool _spectrumReady;
        static MeterManager()
        {
            // readings used by varius meter items such as Text Overlay
            _custom_readings = new CustomReadings[2];
            _custom_readings[0] = new CustomReadings(1);
            _custom_readings[1] = new CustomReadings(2);

            // image fetcher
            _image_fetcher = new ImageFetcher();

            // static constructor
            _rx1VHForAbove = false;
            _rx2VHForAbove = false;
            _delegatesAdded = false;
            _console = null;
            _finishedSetup = false;
            _readings = new Dictionary<int, clsReadings>();
            _meters = new Dictionary<string, clsMeter>();
            _readingIgnore = new Dictionary<int, bool>();

            //_power = false;
            _currentHPSDRmodel = HPSDRModel.HERMES;
            _alexPresent = false;
            _paPresent = false;
            _apolloPresent = false;
            _transverterIndex = -1; // no transverter
            //_spectrumReady = false;

            _image_cache = new Dictionary<string, System.Drawing.Bitmap>();
            _image_streamdata_cache = new Dictionary<string, MemoryStream>();

            // two sets of readings, for each trx
            _readings.Add(1, new clsReadings());
            _readings.Add(2, new clsReadings());

            // two trx reading ignore flags
            _readingIgnore.Add(1, false);
            _readingIgnore.Add(2, false);

            _meterThreadRunning = false;

            _openHPSDR_appdatapath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\OpenHPSDR";
        }

        internal class CustomReadings
        {
            private ConcurrentDictionary<Reading, float> _readings_values;
            private ConcurrentDictionary<string, object> _readings_text_objects;
            private int _rx;

            public CustomReadings(int rx)
            {
                _rx = rx;
                _readings_values = new ConcurrentDictionary<Reading, float>();
                _readings_text_objects = new ConcurrentDictionary<string, object>();
            }
            private string formatNumber(double number)
            {
                string numberString = number.ToString("F6", CultureInfo.InvariantCulture);
                int decimalPointIndex = numberString.IndexOf('.');
                if (decimalPointIndex != -1 && numberString.Length > decimalPointIndex + 3)
                {
                    numberString = numberString.Insert(decimalPointIndex + 4, ".");
                }
                return numberString;
            }
            private string formatElapsedTimeCompact(long elapsedSeconds)
            {
                long seconds = elapsedSeconds % 60;
                long minutes = (elapsedSeconds / 60) % 60;
                long hours = (elapsedSeconds / 3600) % 24;
                long days = (elapsedSeconds / 86400) % 365;
                long years = elapsedSeconds / 31536000;

                if (years > 0)
                {
                    return $"{years}yr {hours:D2}:{minutes:D2}:{seconds:D2}";
                }
                else if (days > 0)
                {
                    return $"{days}d {hours:D2}:{minutes:D2}:{seconds:D2}";
                }
                else
                {
                    return $"{hours:D2}:{minutes:D2}:{seconds:D2}";
                }
            }
            private string formatElapsedTime(long elapsedSeconds)
            {
                long seconds = elapsedSeconds % 60;
                long minutes = (elapsedSeconds / 60) % 60;
                long hours = (elapsedSeconds / 3600) % 24;
                long days = (elapsedSeconds / 86400) % 365;
                long years = elapsedSeconds / 31536000;

                if (years > 0)
                {
                    return $"{years} year{(years > 1 ? "s" : "")} {days} day{(days > 1 ? "s" : "")} {hours} hour{(hours > 1 ? "s" : "")}";
                }
                else if (days > 0)
                {
                    return $"{days} day{(days > 1 ? "s" : "")} {hours} hour{(hours > 1 ? "s" : "")} {minutes} minute{(minutes > 1 ? "s" : "")}";
                }
                else if (hours > 0)
                {
                    return $"{hours} hour{(hours > 1 ? "s" : "")} {minutes} minute{(minutes > 1 ? "s" : "")} {seconds} second{(seconds > 1 ? "s" : "")}";
                }
                else if (minutes > 0)
                {
                    return $"{minutes} minute{(minutes > 1 ? "s" : "")} {seconds} second{(seconds > 1 ? "s" : "")}";
                }
                else
                {
                    return $"{seconds} second{(seconds > 1 ? "s" : "")}";
                }
            }
            public List<string> GetPlaceholders(string text)
            {
                List<string> result = new List<string>();
                int length = text.Length;
                for (int i = 0; i < length; i++)
                {
                    if (text[i] == '%')
                    {
                        if (i + 1 < length) // Ensure there's at least one character after the current %
                        {
                            int end = text.IndexOf('%', i + 1);
                            if (end != -1)
                            {
                                if (end - i > 1) // Ensure there's something between the %
                                {
                                    result.Add(text.Substring(i + 1, end - i - 1));
                                }
                                i = end;
                            }
                        }
                    }
                }
                return result;
            }
            public void TakeReading(Reading reading)
            {
                if (_readings_values.ContainsKey(reading))
                {
                    _readings_values[reading] = getReading(_rx, reading, true);
                }
            }
            public bool IsCustomString(string custom)
            {
                bool bRet = false;
                switch (custom)
                {
                    case "time_utc":
                    case "time_utc_int":
                    case "time_loc":
                    case "time_loc_int":
                    case "date_utc":
                    case "date_utc_int":
                    case "date_loc":
                    case "date_loc_int":
                    case "vfoa":
                    case "vfob":
                    case "vfoasub":
                    case "vfoa_double":
                    case "vfob_double":
                    case "vfoasub_double":
                    case "band_vfoa":
                    case "band_vfob":
                    case "band_vfoasub":
                    case "mode_vfoa":
                    case "mode_vfob":
                    case "subrx":
                    case "filter_vfoa":
                    case "filter_vfob":
                    case "filter_vfoa_name":
                    case "filter_vfob_name":
                    case "split":
                    case "qso_time":
                    case "qso_time_short":
                    case "qso_time_int":
                    case "tb_qso_time":
                    case "tb_qso_time_short":
                    case "tb_qso_time_int":
                    case "mox":
                    case "cfc":
                    case "comp":
                    case "lev":
                    case "rx2":
                    case "tx_eq":
                    case "bandtext_vfoa":
                    case "bandtext_vfob":
                    case "nf":
                    case "tune_step":
                    case "pa_profile":
                    case string c1 when c1.StartsWith("discord_general"):
                    case string c2 when c2.StartsWith("discord_bot"):
                        bRet = true;
                        break;
                }
                return bRet;
            }
            public object GetReading(string reading, clsMeter owningMeter)
            {
                if (!IsCustomString(reading.ToLower()))
                {
                    bool ok = Enum.TryParse<Reading>(reading.ToUpper(), out Reading tmpReading);
                    if (ok)
                    {
                        ok = _readings_values.TryGetValue(tmpReading, out float value);
                        if (ok)
                        {
                            _readings[_rx].UseReading(tmpReading);
                            return value;
                        }
                        else
                        {
                            return 0;
                        }
                    }
                }

                // additional string based custom readings such as time_utc etc, we can just update these
                // as they are obtained
                DateTime now = DateTime.Now;
                DateTime UTCnow = DateTime.UtcNow;
                string key = reading.ToLower();
                switch (key)
                {
                    case "time_utc":
                        _readings_text_objects[key] = UTCnow.ToString("HH:mm:ss");
                        break;
                    case "time_loc":
                        _readings_text_objects[key] = now.ToString("HH:mm:ss");
                        break;
                    case "date_utc":
                        _readings_text_objects[key] = UTCnow.ToString("ddd d MMM yyyy");
                        break;
                    case "date_loc":
                        _readings_text_objects[key] = now.ToString("ddd d MMM yyyy");
                        break;
                    case "time_utc_int":
                        _readings_text_objects[key] = (int)(UTCnow.Hour * 10000 + UTCnow.Minute * 100 + UTCnow.Second);
                        break;
                    case "time_loc_int":
                        _readings_text_objects[key] = (int)(now.Hour * 10000 + now.Minute * 100 + now.Second);
                        break;
                    case "date_utc_int":
                        _readings_text_objects[key] = (int)(UTCnow.Hour * 10000 + UTCnow.Minute * 100 + UTCnow.Second);
                        break;
                    case "date_loc_int":
                        _readings_text_objects[key] = (int)(now.Hour * 10000 + now.Minute * 100 + now.Second);
                        break;
                    case "vfoa":
                        if (_rx == 1)
                            _readings_text_objects[key] = formatNumber(owningMeter.VfoA);
                        else
                            _readings_text_objects[key] = "";
                        break;
                    case "vfob":
                        if (owningMeter.RX2Enabled && _rx == 1)
                            _readings_text_objects[key] = "";
                        else
                            _readings_text_objects[key] = formatNumber(owningMeter.VfoB);
                        break;
                    case "vfoasub":
                        if (owningMeter.VfoSub >= 0 && _rx == 1 && owningMeter.RX2Enabled && (owningMeter.Split || owningMeter.MultiRxEnabled)) // when -999.999
                            _readings_text_objects[key] = formatNumber(owningMeter.VfoSub);
                        else
                            _readings_text_objects[key] = "";
                        break;
                    case "vfoa_double":
                        if (_rx == 1)
                            _readings_text_objects[key] = Math.Round(owningMeter.VfoA, 6);
                        else
                            _readings_text_objects[key] = "";
                        break;
                    case "vfob_double":
                        if (owningMeter.RX2Enabled && _rx == 1)
                            _readings_text_objects[key] = "";
                        else
                            _readings_text_objects[key] = Math.Round(owningMeter.VfoB, 6);
                        break;
                    case "vfoasub_double":
                        if (owningMeter.VfoSub >= 0 && _rx == 1 && owningMeter.RX2Enabled && (owningMeter.Split || owningMeter.MultiRxEnabled)) // when -999.999
                            _readings_text_objects[key] = Math.Round(owningMeter.VfoSub, 6);
                        else
                            _readings_text_objects[key] = "";
                        break;
                    case "band_vfoa":
                        _readings_text_objects[key] = BandStackManager.BandToString(owningMeter.BandVfoA).ToLower();
                        break;
                    case "band_vfob":
                        _readings_text_objects[key] = BandStackManager.BandToString(owningMeter.BandVfoA).ToLower();
                        break;
                    case "band_vfoasub":
                        _readings_text_objects[key] = BandStackManager.BandToString(owningMeter.BandVfoASub).ToLower();
                        break;
                    case "mode_vfoa":
                        _readings_text_objects[key] = owningMeter.ModeVfoA.ToString();
                        break;
                    case "mode_vfob":
                        _readings_text_objects[key] = owningMeter.ModeVfoB.ToString();
                        break;
                    case "subrx":
                        _readings_text_objects[key] = owningMeter.MultiRxEnabled ? "SubRX" : "";
                        break;
                    case "filter_vfoa":
                        _readings_text_objects[key] = owningMeter.FilterVfoA.ToString();
                        break;
                    case "filter_vfob":
                        _readings_text_objects[key] = owningMeter.FilterVfoB.ToString();
                        break;
                    case "filter_vfoa_name":
                        _readings_text_objects[key] = owningMeter.FilterVfoAName;
                        break;
                    case "filter_vfob_name":
                        _readings_text_objects[key] = owningMeter.FilterVfoBName;
                        break;
                    case "split":
                        _readings_text_objects[key] = owningMeter.Split ? (owningMeter.QuickSplitEnabled ? "QSPLIT" : "SPLIT") : "";
                        break;
                    case "qso_time":
                        _readings_text_objects[key] = formatElapsedTime(owningMeter.QsoDurationSeconds);
                        break;
                    case "qso_time_short":
                        _readings_text_objects[key] = formatElapsedTimeCompact(owningMeter.QsoDurationSeconds);
                        break;
                    case "tb_qso_time":
                        _readings_text_objects[key] = _console.QSOTimerEnabled ? formatElapsedTime(_console.QSOTimerSeconds) : "";
                        break;
                    case "tb_qso_time_short":
                        _readings_text_objects[key] = _console.QSOTimerEnabled ? formatElapsedTimeCompact(_console.QSOTimerSeconds) : "";
                        break;
                    case "qso_time_int":
                        {
                            TimeSpan time = TimeSpan.FromSeconds(owningMeter.QsoDurationSeconds);
                            int hours = time.Hours;
                            int minutes = time.Minutes;
                            int seconds = time.Seconds;
                            _readings_text_objects[key] = (int)(hours * 10000 + minutes * 100 + seconds);
                        }
                        break;
                    case "tb_qso_time_int":
                        {
                            TimeSpan time = TimeSpan.FromSeconds(_console.QSOTimerSeconds);
                            int hours = time.Hours;
                            int minutes = time.Minutes;
                            int seconds = time.Seconds;
                            _readings_text_objects[key] = (int)(hours * 10000 + minutes * 100 + seconds);
                        }
                        break;
                    case "mox":
                        _readings_text_objects[key] = owningMeter.MOX ? "MOX" : "";
                        break;
                    case "cfc":
                        _readings_text_objects[key] = owningMeter.CFCEnabled ? "CFC" : "";
                        break;
                    case "comp":
                        _readings_text_objects[key] = owningMeter.CompandEnabled ? "COMP" : "";
                        break;
                    case "lev":
                        _readings_text_objects[key] = owningMeter.LevelerEnabled ? "LEVELER" : "";
                        break;
                    case "rx2":
                        _readings_text_objects[key] = owningMeter.RX2Enabled ? "RX2 On" : "";
                        break;
                    case "tx_eq":
                        _readings_text_objects[key] = owningMeter.TXEQEnabled ? "TXEQ" : "";
                        break;
                    case "bandtext_vfoa":
                        _readings_text_objects[key] = owningMeter.VFOABandText;
                        break;
                    case "bandtext_vfob":
                        _readings_text_objects[key] = owningMeter.VFOBBandText;
                        break;
                    case "nf":
                        if(_rx == 1)
                            _readings_text_objects[key] = _console.LastNFRX1;
                        else
                            _readings_text_objects[key] = _console.LastNFRX2;
                        break;
                    case "tune_step":
                        returnTuneStep(key);
                        break;
                    case "pa_profile":
                        returnPAProfile(key);
                        break;
                    case string c1 when c1.StartsWith("discord_general"):
                        {
                            int part = getIntPart(key, "discord_general");
                            _readings_text_objects[key] = ThetisBotDiscord.GetMessagesString(1271453529361481842, part, true);
                        }
                        break;
                    case string c2 when c2.StartsWith("discord_bot"):
                        {
                            int part = getIntPart(key, "discord_bot");
                            _readings_text_objects[key] = ThetisBotDiscord.GetMessagesString(1297325528336044144, part, false);
                        }
                        break;
                }

                if (_readings_text_objects.ContainsKey(key))
                {
                    bool ok = _readings_text_objects.TryGetValue(key, out object tmp);
                    if (ok)
                        return tmp;
                    else
                        return "";
                }
                return "";
            }
            private static int getIntPart(string input, string prefix)
            {
                if (input == prefix)
                {
                    return 0;
                }

                if (input.StartsWith(prefix + "="))
                {
                    string[] parts = input.Split('=');
                    if (parts.Length == 2 && int.TryParse(parts[1], out int number))
                    {
                        return number;
                    }
                }

                return 0;
            }
            public void returnTuneStep(string key)
            {
                if (_console != null)
                {
                    try
                    {
                        _readings_text_objects[key] = _console.TuneStepList[_console.TuneStepIndex].Name;
                    }
                    catch { }
                }
            }
            public void returnPAProfile(string key)
            {
                if (_console != null)
                {
                    try
                    {
                        _readings_text_objects[key] = _console.PAProfile;
                    }
                    catch { }
                }
            }
            public void UpdateReadings(string text)
            {
                //add readings required
                addReading(Reading.SWR, text);
                addReading(Reading.SIGNAL_STRENGTH, text);
                addReading(Reading.AVG_SIGNAL_STRENGTH, text);
                addReading(Reading.PWR, text);
                addReading(Reading.REVERSE_PWR, text);
                addReading(Reading.MIC, text);
                addReading(Reading.MIC_PK, text);
                addReading(Reading.ADC_PK, text);
                addReading(Reading.ADC_AV, text);
                addReading(Reading.AGC_PK, text);
                addReading(Reading.AGC_AV, text);
                addReading(Reading.AGC_GAIN, text);
                addReading(Reading.LEVELER, text);
                addReading(Reading.LEVELER_PK, text);
                addReading(Reading.LVL_G, text);
                addReading(Reading.ALC, text);
                addReading(Reading.ALC_PK, text);
                addReading(Reading.ALC_G, text);
                addReading(Reading.ALC_GROUP, text);
                addReading(Reading.CFC_AV, text);
                addReading(Reading.CFC_PK, text);
                addReading(Reading.CFC_G, text);
                addReading(Reading.COMP, text);
                addReading(Reading.COMP_PK, text);
                addReading(Reading.ESTIMATED_PBSNR, text);
                addReading(Reading.VOLTS, text);
                addReading(Reading.AMPS, text);

                addReadingText("time_utc", text);
                addReadingText("time_loc", text);
                addReadingText("date_utc", text);
                addReadingText("date_loc", text);
                addReadingText("time_utc_int", text);
                addReadingText("time_loc_int", text);
                addReadingText("date_utc_int", text);
                addReadingText("date_loc_int", text);
                addReadingText("vfoa", text);
                addReadingText("vfob", text);
                addReadingText("vfoasub", text);
                addReadingText("vfoa_double", text);
                addReadingText("vfob_double", text);
                addReadingText("vfoasub_double", text);
                addReadingText("band_vfoa", text);
                addReadingText("band_vfob", text);
                addReadingText("band_vfoasub", text);
                addReadingText("mode_vfoa", text);
                addReadingText("mode_vfob", text);
                addReadingText("subrx", text);
                addReadingText("filter_vfoa", text);
                addReadingText("filter_vfob", text);
                addReadingText("filter_vfoa_name", text);
                addReadingText("filter_vfob_name", text);
                addReadingText("split", text);
                addReadingText("qso_time", text);
                addReadingText("qso_time_short", text);
                addReadingText("qso_time_int", text);
                addReadingText("tb_qso_time", text);
                addReadingText("tb_qso_time_short", text);
                addReadingText("tb_qso_time_int", text);
                addReadingText("mox", text);
                addReadingText("cfc", text);
                addReadingText("comp", text);
                addReadingText("lev", text);
                addReadingText("rx2", text);
                addReadingText("tx_eq", text);
                addReadingText("bandtext_vfoa", text);
                addReadingText("bandtext_vfob", text);
                addReadingText("nf", text);
                addReadingText("tune_step", text);
                addReadingText("pa_profile", text);
                addReadingText("discord_general", text);
                addReadingText("discord_general=", text);
                addReadingText("discord_bot", text);
                addReadingText("discord_bot=", text);
            }
            private void addReading(Reading reading, string text)
            {
                if (!_readings_values.ContainsKey(reading) && text.Contains("%" + reading.ToString().ToLower() + "%")) _readings_values.TryAdd(reading, 0f);
            }
            private void addReadingText(string reading, string text)
            {
                if (!_readings_text_objects.ContainsKey(reading) && text.Contains("%" + reading + "%")) _readings_text_objects.TryAdd(reading, "");
            }
        }
        public class clsIGSettings
        {
            // TODO change all these unique settings to a Dictionary collection   _settings = new Dictionary<string, object>
            private ConcurrentDictionary<string, object> _settings;
            //

            private int _updateInterval;
            private float _decay;
            private float _attack;
            private int _historyDuration;
            private bool _shadow;
            private bool _showHistory;
            private System.Drawing.Color _historyColor;
            private bool _peakHold;
            private System.Drawing.Color _peakHoldMarkerColor;
            private System.Drawing.Color _lowColor;
            private System.Drawing.Color _highColor;
            private System.Drawing.Color _titleColor;
            private Reading _readingSource;
            private System.Drawing.Color _colour;
            private System.Drawing.Color _markerColour;
            private System.Drawing.Color _subMarkerColour;
            private clsBarItem.BarStyle _barStyle;
            private string _text_1;
            private string _text_2;
            private bool _fadeOnRx;
            private bool _fadeOnTx;
            private bool _showType;
            private System.Drawing.Color _segmentedSolidLowColour;
            private System.Drawing.Color _segmentedSolidHighColour;
            private bool _peakValue;
            private System.Drawing.Color _peakValueColour;
            private float _eyeScale;
            private float _spacerPadding;
            private bool _back_panel;
            private float _eyeBezelScale;
            private bool _average;
            private bool _darkMode;
            private float _maxPower;
            private System.Drawing.Color _powerScaleColour;
            private clsBarItem.Units _units;
            private bool _showMarker;
            private bool _showSubMarker;
            private bool _hasSubIndicators;
            private int _ignoreHistoryDuration;
            private string _font_family_1;
            private FontStyle _font_style_1;
            private float _font_size_1;
            private string _font_family_2;
            private FontStyle _font_style_2;
            private float _font_size_2;

            private Guid[] _mmio_guid;
            private string[] _mmio_variable;

            public clsIGSettings()
            {
                //
                _settings = new ConcurrentDictionary<string, object>();
                //

                _hasSubIndicators = false;
                _readingSource = Reading.NONE;
                _barStyle = clsBarItem.BarStyle.None;
                _maxPower = CurrentPowerRating;
                _units = clsBarItem.Units.DBM;
                _ignoreHistoryDuration = 2000;
                _historyDuration = 50;
                _text_1 = "";
                _text_2 = "";
                _font_family_1 = "";
                _font_family_2 = "";

                _mmio_guid = new Guid[10];
                _mmio_variable = new string[10];

                for (int i = 0; i < _mmio_guid.Length; i++)
                {
                    _mmio_guid[i] = Guid.Empty;
                    _mmio_variable[i] = "--DEFAULT--";
                }
            }

            public void SetSetting(string setting, object value)
            {
                if (_settings.ContainsKey(setting))
                    _settings[setting] = value;
                else
                    _settings.TryAdd(setting, value);
            }
            public void SetSetting<T>(string setting, T value)
            {
                if (_settings.ContainsKey(setting))
                    _settings[setting] = value;
                else
                    _settings.TryAdd(setting, value);
            }
            public object GetSetting(string setting, Type type)
            {
                if (_settings.ContainsKey(setting))
                    return _settings[setting];
                else
                {
                    if (type == typeof(int))
                        return (int)0;
                    else if (type == typeof(float))
                        return (float)0;
                    else if (type == typeof(double))
                        return (double)0;
                    else if (type == typeof(bool))
                        return (bool)false;
                    else if (type == typeof(System.Drawing.Color))
                        return System.Drawing.Color.Gray;
                    else if (type == typeof(Guid))
                        return Guid.Empty;
                    else if (type == typeof(Reading))
                        return Reading.NONE;
                    else if (type == typeof(clsBarItem.BarStyle))
                        return clsBarItem.BarStyle.None;
                    else if (type == typeof(clsBarItem.Units))
                        return clsBarItem.Units.DBM;
                    else if (type == typeof(FontStyle))
                        return FontStyle.Regular;
                    else
                        return "";
                }
            }
            public T GetSetting<T>(string setting, bool validate = false, T min = default(T), T max = default(T), T defaultValue = default(T))
            {
                T value;

                if (_settings.ContainsKey(setting))
                    value = (T)_settings[setting];
                else
                    value = defaultValue;

                if (validate)// && Comparer<T>.Default.Compare(min, max) != 0)
                {
                    if (Comparer<T>.Default.Compare(value, min) < 0)
                        return min;
                    if (Comparer<T>.Default.Compare(value, max) > 0)
                        return max;
                }

                return value;
            }
            public string ToString2()
            {
                try
                {
                    //1 for version 1
                    string tmp = Common.SerializeToBase64<ConcurrentDictionary<string, object>>(_settings); // 1| signifies version 1 of the serialize for future proofing
                    tmp = tmp.Replace("/", "[backslash]");
                    return "1|" + tmp;
                }
                catch
                {
                    return "";
                }
                //string ret = "";

                //foreach(KeyValuePair<string, object> pair in _settings)
                //{
                //    object val = pair.Value;

                //    ret += pair.Key + "|";

                //    ret += val.GetType().AssemblyQualifiedName + "|";

                //    if (val is int i)
                //    {
                //        ret += i.ToString() + "|";
                //    }
                //    else if (val is System.Drawing.Color clr)
                //    {
                //        ret += ColorTranslator.ToHtml(clr) + "|";
                //    }
                //    else if (val is float flt)
                //    {
                //        ret += flt.ToString("f2") + "|";
                //    }
                //    else if (val is double dbl)
                //    {
                //        ret += dbl.ToString("f4") + "|";
                //    }
                //    else if (val is bool bl)
                //    {
                //        ret += bl.ToString().ToLower() + "|";
                //    }
                //    else
                //    {
                //        ret += (val.ToString()).Replace("|", "_+>>++<<+_") + "|";
                //    }
                //}

                //if (!string.IsNullOrEmpty(ret))
                //{
                //    //drop last |
                //    ret = ret.Substring(0, ret.Length - 1);
                //}

                //return ret;
            }
            public bool TryParse2(string str)
            {
                try
                {
                    string[] parts = str.Split('|');
                    if (parts.Length != 2) return false;

                    if (parts[0] == "1") // 1 signifies version 1 of the serialize for future proofing
                    {
                        string tmp = parts[1].Replace("[backslash]", "/");
                        _settings = Common.DeserializeFromBase64<ConcurrentDictionary<string, object>>(tmp);
                    }

                    return true;
                }
                catch
                {
                    return false;
                }

                //bool ok = true;

                //string[] settings = str.Split('|');
                //if (settings.Length < 3 && settings.Length % 3 != 0) ok = false;

                //if(ok)
                //{
                //    for (int i = 0; i < settings.Length; i += 3) 
                //    { 
                //        string setting = settings[i];
                //        string type = settings[i + 1];
                //        string val = settings[i + 2];

                //        Type tpe = Type.GetType(type);
                //        object typedValue = Common.ConvertToType(val, tpe);

                //        SetSetting(setting, typedValue);
                //    }
                //}

                //return ok;
            }

            //public override string ToString()
            //{
            //    string sRet = _updateInterval.ToString() + "|" +
            //        _decay.ToString("f4") + "|" +
            //        _attack.ToString("f4") + "|" +
            //        _historyDuration.ToString() + "|" +
            //        _shadow.ToString() + "|" +
            //        _showHistory.ToString() + "|" +
            //        Common.ColourToString(_historyColor) + "|" +
            //        _peakHold.ToString() + "|" +
            //        Common.ColourToString(_peakHoldMarkerColor) + "|" +
            //        Common.ColourToString(_lowColor) + "|" +
            //        Common.ColourToString(_highColor) + "|" +
            //        Common.ColourToString(_titleColor) + "|" +
            //        _readingSource.ToString() + "|" +
            //        Common.ColourToString(_colour) + "|" +
            //        Common.ColourToString(_markerColour) + "|" +
            //        _barStyle.ToString() + "|" +
            //        _text_1.Replace("|", "") + "|" +
            //        _fadeOnRx.ToString() + "|" +
            //        _fadeOnTx.ToString() + "|" +
            //        _showType.ToString() + "|" +
            //        Common.ColourToString(_segmentedSolidLowColour) + "|" +
            //        _peakValue.ToString() + "|" +
            //        Common.ColourToString(_peakValueColour) + "|" +
            //        _eyeScale.ToString("f4") + "|" +
            //        _average.ToString() + "|" +
            //        _darkMode.ToString() + "|" +
            //        _maxPower.ToString("f2") + "|" +
            //        _units.ToString() + "|" +
            //        Common.ColourToString(_segmentedSolidHighColour) + "|" +
            //        _showMarker.ToString() + "|" +
            //        Common.ColourToString(_subMarkerColour) + "|" +
            //        _showSubMarker.ToString() + "|" +
            //        _eyeBezelScale.ToString("f4") + "|" +
            //        Common.ColourToString(_powerScaleColour) + "|" +
            //        _ignoreHistoryDuration.ToString() + "|" +
            //        _spacerPadding.ToString("f4") + "|" +
            //        _back_panel.ToString() + "|" +
            //        _text_2.Replace("|", "") + "|" +
            //        _font_family_1.Replace("|", "++><++") + "|" +
            //        _font_family_2.Replace("|", "++><++") + "|" +
            //        _font_style_1.ToString() + "|" +
            //        _font_style_2.ToString() + "|" +
            //        _font_size_1.ToString("f4") + "|" +
            //        _font_size_2.ToString("f4") + "|";

            //    for (int i = 0; i < _mmio_guid.Length; i++)
            //    {
            //        sRet += _mmio_guid[i].ToString() + "|";
            //        sRet += _mmio_variable[i] + "|";
            //    }

            //    if (!string.IsNullOrEmpty(sRet))
            //    {
            //        //drop last |
            //        sRet = sRet.Substring(0, sRet.Length - 1);
            //    }

            //    return sRet;
            //}
            public bool TryParse(string str)
            {
                if (str == "") return false;

                int tmpInt = 0;
                float tmpFloat = 0;
                System.Drawing.Color tmpColour = System.Drawing.Color.White;
                bool tmpBool = false;
                Reading tmpReading = Reading.NONE;
                FontStyle tmpFontStyle = FontStyle.Regular;
                clsBarItem.BarStyle tmpBarStyle = clsBarItem.BarStyle.None;
                clsBarItem.Units tmpUnit = clsBarItem.Units.DBM;
                Guid tmpGuid = Guid.Empty;

                bool bOk = false;

                string[] tmp = str.Split('|');

                if (tmp.Length >= 32)
                {
                    bOk = int.TryParse(tmp[0], out tmpInt); if (bOk) { _updateInterval = tmpInt; }
                    if (bOk) bOk = float.TryParse(tmp[1], out tmpFloat); if (bOk) { _decay = tmpFloat; }
                    if (bOk) bOk = float.TryParse(tmp[2], out tmpFloat); if (bOk) { _attack = tmpFloat; }
                    if (bOk) bOk = int.TryParse(tmp[3], out tmpInt); if (bOk) { _historyDuration = tmpInt; }
                    if (bOk) bOk = bool.TryParse(tmp[4], out tmpBool); if (bOk) { _shadow = tmpBool; }
                    if (bOk) bOk = bool.TryParse(tmp[5], out tmpBool); if (bOk) { _showHistory = tmpBool; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[6]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _historyColor = tmpColour; }
                    if (bOk) bOk = bool.TryParse(tmp[7], out tmpBool); if (bOk) { _peakHold = tmpBool; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[8]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _peakHoldMarkerColor = tmpColour; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[9]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _lowColor = tmpColour; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[10]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _highColor = tmpColour; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[11]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _titleColor = tmpColour; }
                    if (bOk) bOk = Enum.TryParse<Reading>(tmp[12], out tmpReading); if (bOk) { _readingSource = tmpReading; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[13]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _colour = tmpColour; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[14]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _markerColour = tmpColour; }
                    if (bOk) bOk = Enum.TryParse<clsBarItem.BarStyle>(tmp[15], out tmpBarStyle); if (bOk) { _barStyle = tmpBarStyle; }
                    if (bOk) _text_1 = tmp[16].Replace("|", ""); // old method needs to do this, but will cause problems for LedIndictaor or statement
                    if (bOk) bOk = bool.TryParse(tmp[17], out tmpBool); if (bOk) { _fadeOnRx = tmpBool; }
                    if (bOk) bOk = bool.TryParse(tmp[18], out tmpBool); if (bOk) { _fadeOnTx = tmpBool; }
                    if (bOk) bOk = bool.TryParse(tmp[19], out tmpBool); if (bOk) { _showType = tmpBool; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[20]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _segmentedSolidLowColour = tmpColour; }
                    if (bOk) bOk = bool.TryParse(tmp[21], out tmpBool); if (bOk) { _peakValue = tmpBool; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[22]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _peakValueColour = tmpColour; }
                    if (bOk) bOk = float.TryParse(tmp[23], out tmpFloat); if (bOk) { _eyeScale = tmpFloat; _eyeBezelScale = _eyeScale; } // both incase recoving old data
                    if (bOk) bOk = bool.TryParse(tmp[24], out tmpBool); if (bOk) { _average = tmpBool; }
                    if (bOk) bOk = bool.TryParse(tmp[25], out tmpBool); if (bOk) { _darkMode = tmpBool; }
                    if (bOk) bOk = float.TryParse(tmp[26], out tmpFloat); if (bOk) { _maxPower = tmpFloat; }
                    if (bOk) bOk = Enum.TryParse<clsBarItem.Units>(tmp[27], out tmpUnit); if (bOk) { _units = tmpUnit; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[28]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _segmentedSolidHighColour = tmpColour; }
                    if (bOk) bOk = bool.TryParse(tmp[29], out tmpBool); if (bOk) { _showMarker = tmpBool; }
                    if (bOk) tmpColour = Common.ColourFromString(tmp[30]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _subMarkerColour = tmpColour; }
                    if (bOk) bOk = bool.TryParse(tmp[31], out tmpBool); if (bOk) { _showSubMarker = tmpBool; }
                }

                // this is due to new versions requiring more and more settings
                if (bOk && tmp.Length >= 33)
                {
                    if (bOk) bOk = float.TryParse(tmp[32], out tmpFloat); if (bOk) { _eyeBezelScale = tmpFloat; }
                }
                if (bOk && tmp.Length >= 34)
                {
                    if (bOk) tmpColour = Common.ColourFromString(tmp[33]); bOk = tmpColour != System.Drawing.Color.Empty; if (bOk) { _powerScaleColour = tmpColour; }
                }
                if(bOk && tmp.Length >= 35)
                {
                    if (bOk) bOk = int.TryParse(tmp[34], out tmpInt); if (bOk) { _ignoreHistoryDuration = tmpInt; }
                }
                if (bOk && tmp.Length >= 44) //[2.10.3.6]MW0LGE added for dev_6
                {
                    if (bOk) bOk = float.TryParse(tmp[35], out tmpFloat); if (bOk) { _spacerPadding = tmpFloat; }
                    if (bOk) bOk = bool.TryParse(tmp[36], out tmpBool); if (bOk) { _back_panel = tmpBool; }
                    if (bOk) _text_2 = tmp[37].Replace("|", "");
                    if (bOk) _font_family_1 = tmp[38].Replace("++><++", "|");
                    if (bOk) _font_family_2 = tmp[39].Replace("++><++", "|");
                    if (bOk) bOk = Enum.TryParse<FontStyle>(tmp[40], out tmpFontStyle); if (bOk) { _font_style_1 = tmpFontStyle; }
                    if (bOk) bOk = Enum.TryParse<FontStyle>(tmp[41], out tmpFontStyle); if (bOk) { _font_style_2 = tmpFontStyle; }
                    if (bOk) bOk = float.TryParse(tmp[42], out tmpFloat); if (bOk) { _font_size_1 = tmpFloat; }
                    if (bOk) bOk = float.TryParse(tmp[43], out tmpFloat); if (bOk) { _font_size_2 = tmpFloat; }
                }
                if (bOk && tmp.Length >= 64)
                {
                    for (int i = 0; i < _mmio_guid.Length; i++) //44,45, 46,47, 48,49, 50,51, 52,53, 54,55, 56,57, 58,59, 60,61, 62,63
                    {
                        if (bOk) bOk = Guid.TryParse(tmp[44 + (i*2)], out tmpGuid); if (bOk) { _mmio_guid[i] = tmpGuid; }
                        if (bOk) _mmio_variable[i] = tmp[45 + (i*2)];
                    }
                }

                //
                SetSetting("_updateInterval", _updateInterval);
                SetSetting("_decay", _decay);
                SetSetting("_attack", _attack);
                SetSetting("_historyDuration", _historyDuration);
                SetSetting("_shadow", _shadow);
                SetSetting("_showHistory", _showHistory);
                SetSetting("_historyColor", _historyColor);
                SetSetting("_peakHold", _peakHold);
                SetSetting("_peakHoldMarkerColor", _peakHoldMarkerColor);
                SetSetting("_lowColor", _lowColor);
                SetSetting("_highColor", _highColor);
                SetSetting("_titleColor", _titleColor);
                SetSetting("_readingSource", _readingSource);
                SetSetting("_colour", _colour);
                SetSetting("_markerColour", _markerColour);
                SetSetting("_barStyle", _barStyle);
                SetSetting("_text_1", _text_1);
                SetSetting("_fadeOnRx", _fadeOnRx);
                SetSetting("_fadeOnTx", _fadeOnTx);
                SetSetting("_showType", _showType);
                SetSetting("_segmentedSolidLowColour", _segmentedSolidLowColour);
                SetSetting("_peakValue", _peakValue);
                SetSetting("_peakValueColour", _peakValueColour);
                SetSetting("_eyeScale", _eyeScale);
                SetSetting("_average", _average);
                SetSetting("_darkMode", _darkMode);
                SetSetting("_maxPower", _maxPower);
                SetSetting("_units", _units);
                SetSetting("_segmentedSolidHighColour", _segmentedSolidHighColour);
                SetSetting("_showMarker", _showMarker);
                SetSetting("_subMarkerColour", _subMarkerColour);
                SetSetting("_showSubMarker", _showSubMarker);

                SetSetting("_eyeBezelScale", _eyeBezelScale);
                SetSetting("_powerScaleColour", _powerScaleColour);
                SetSetting("_ignoreHistoryDuration", _ignoreHistoryDuration);
                SetSetting("_spacerPadding", _spacerPadding);
                SetSetting("_back_panel", _back_panel);
                SetSetting("_text_2", _text_2);
                SetSetting("_font_family_1", _font_family_1);
                SetSetting("_font_family_2", _font_family_2);
                SetSetting("_font_style_1", _font_style_1);
                SetSetting("_font_style_2", _font_style_2);
                SetSetting("_font_size_1", _font_size_1);
                SetSetting("_font_size_2", _font_size_2);

                for (int i = 0; i < _mmio_guid.Length; i++)
                {
                    SetMMIOGuid(i, _mmio_guid[i]);
                    SetMMIOVariable(i, _mmio_variable[i]);
                }
                //

                return bOk;
            }
            public bool SubIndicators { get { return (bool)GetSetting("_hasSubIndicators", typeof(bool)); } set { SetSetting("_hasSubIndicators", value); } }
            public int UpdateInterval { get { return (int)GetSetting("_updateInterval", typeof(int)); } set { SetSetting("_updateInterval", value); } }
            public float DecayRatio { get { return (float)GetSetting("_decay", typeof(float)); } set { SetSetting("_decay", value); } }
            public float AttackRatio { get { return (float)GetSetting("_attack", typeof(float)); } set { SetSetting("_attack", value); } }
            public int HistoryDuration { get { return (int)GetSetting("_historyDuration", typeof(int)); } set { SetSetting("_historyDuration", value); } }
            public int IgnoreHistoryDuration { get { return (int)GetSetting("_ignoreHistoryDuration", typeof(int)); } set { SetSetting("_ignoreHistoryDuration", value); } }
            public bool Shadow { get { return (bool)GetSetting("_shadow", typeof(bool)); } set { SetSetting("_shadow", value); } }
            public bool ShowHistory { get { return (bool)GetSetting("_showHistory", typeof(bool)); } set { SetSetting("_showHistory", value); } }
            public System.Drawing.Color HistoryColor { get { return (System.Drawing.Color)GetSetting("_historyColor", typeof(System.Drawing.Color)); } set { SetSetting("_historyColor", value); } }
            public bool PeakHold { get { return (bool)GetSetting("_peakHold", typeof(bool)); } set { SetSetting("_peakHold", value); } }
            public System.Drawing.Color PeakHoldMarkerColor { get { return (System.Drawing.Color)GetSetting("_peakHoldMarkerColor", typeof(System.Drawing.Color)); } set { SetSetting("_peakHoldMarkerColor", value); } }
            public System.Drawing.Color LowColor { get { return (System.Drawing.Color)GetSetting("_lowColor", typeof(System.Drawing.Color)); } set { SetSetting("_lowColor", value); } }
            public System.Drawing.Color HighColor { get { return (System.Drawing.Color)GetSetting("_highColor", typeof(System.Drawing.Color)); } set { SetSetting("_highColor", value); } }
            public System.Drawing.Color TitleColor { get { return (System.Drawing.Color)GetSetting("_titleColor", typeof(System.Drawing.Color)); } set { SetSetting("_titleColor", value); } }
            public Reading ReadingSource { get { return (Reading)GetSetting("_readingSource", typeof(Reading)); } set { SetSetting("_readingSource", value); } }
            public System.Drawing.Color Colour { get { return (System.Drawing.Color)GetSetting("_colour", typeof(System.Drawing.Color)); } set { SetSetting("_colour", value); } }
            public System.Drawing.Color MarkerColour { get { return (System.Drawing.Color)GetSetting("_markerColour", typeof(System.Drawing.Color)); } set { SetSetting("_markerColour", value); } }
            public System.Drawing.Color SubMarkerColour { get { return (System.Drawing.Color)GetSetting("_subMarkerColour", typeof(System.Drawing.Color)); } set { SetSetting("_subMarkerColour", value); } }
            public bool ShowMarker { get { return (bool)GetSetting("_showMarker", typeof(bool)); } set { SetSetting("_showMarker", value); } }
            public bool ShowSubMarker { get { return (bool)GetSetting("_showSubMarker", typeof(bool)); } set { SetSetting("_showSubMarker", value); } }
            public clsBarItem.BarStyle BarStyle { get { return (clsBarItem.BarStyle)GetSetting("_barStyle", typeof(clsBarItem.BarStyle)); } set { SetSetting("_barStyle", value); } }
            public string Text1 { get { return ((string)GetSetting("_text_1", typeof(string))); } set { SetSetting("_text_1", value); } }
            public string Text2 { get { return ((string)GetSetting("_text_2", typeof(string))); } set { SetSetting("_text_2", value); } }
            public bool FadeOnRx { get { return (bool)GetSetting("_fadeOnRx", typeof(bool)); } set { SetSetting("_fadeOnRx", value); } }
            public bool FadeOnTx { get { return (bool)GetSetting("_fadeOnTx", typeof(bool)); } set { SetSetting("_fadeOnTx", value); } }
            public bool ShowType { get { return (bool)GetSetting("_showType", typeof(bool)); } set { SetSetting("_showType", value); } }
            public System.Drawing.Color SegmentedSolidLowColour { get { return (System.Drawing.Color)GetSetting("_segmentedSolidLowColour", typeof(System.Drawing.Color)); } set { SetSetting("_segmentedSolidLowColour", value); } }
            public System.Drawing.Color SegmentedSolidHighColour { get { return (System.Drawing.Color)GetSetting("_segmentedSolidHighColour", typeof(System.Drawing.Color)); } set { SetSetting("_segmentedSolidHighColour", value); } }
            public bool PeakValue { get { return (bool)GetSetting("_peakValue", typeof(bool)); } set { SetSetting("_peakValue", value); } }
            public System.Drawing.Color PeakValueColour { get { return (System.Drawing.Color)GetSetting("_peakValueColour", typeof(System.Drawing.Color)); } set { SetSetting("_peakValueColour", value); } }
            public float EyeScale { get { return (float)GetSetting("_eyeScale", typeof(float)); } set { SetSetting("_eyeScale", value); } }
            public float SpacerPadding { get { return (float)GetSetting("_spacerPadding", typeof(float)); } set { SetSetting("_spacerPadding", value); } }
            public bool BackPanel { get { return (bool)GetSetting("_back_panel", typeof(bool)); } set { SetSetting("_back_panel", value); } }
            public float EyeBezelScale { get { return (float)GetSetting("_eyeBezelScale", typeof(float)); } set { SetSetting("_eyeBezelScale", value); } }
            public bool Average { get { return (bool)GetSetting("_average", typeof(bool)); } set { SetSetting("_average", value); } }
            public bool DarkMode { get { return (bool)GetSetting("_darkMode", typeof(bool)); } set { SetSetting("_darkMode", value); } }
            public float MaxPower { get { return (float)GetSetting("_maxPower", typeof(float)); } set { SetSetting("_maxPower", value); } }
            public System.Drawing.Color PowerScaleColour { get { return (System.Drawing.Color)GetSetting("_powerScaleColour", typeof(System.Drawing.Color)); } set { SetSetting("_powerScaleColour", value); } }
            public clsBarItem.Units Unit { get { return (clsBarItem.Units)GetSetting("_units", typeof(clsBarItem.Units)); } set { SetSetting("_units", value); } }
            public string FontFamily1 { get { return ((string)GetSetting("_font_family_1", typeof(string))); } set { SetSetting("_font_family_1", value); } }
            public string FontFamily2 { get { return ((string)GetSetting("_font_family_2", typeof(string))); } set { SetSetting("_font_family_2", value); } }
            public FontStyle FontStyle1 { get { return (FontStyle)GetSetting("_font_style_1", typeof(FontStyle)); } set { SetSetting("_font_style_1", value); } }
            public FontStyle FontStyle2 { get { return (FontStyle)GetSetting("_font_style_2", typeof(FontStyle)); } set { SetSetting("_font_style_2", value); } }
            public float FontSize1 { get { return (float)GetSetting("_font_size_1", typeof(float)); } set { SetSetting("_font_size_1", value); } }
            public float FontSize2 { get { return (float)GetSetting("_font_size_2", typeof(float)); } set { SetSetting("_font_size_2", value); } }


            //public bool SubIndicators { get { return _hasSubIndicators; } set { _hasSubIndicators = value; } }
            //public int UpdateInterval { get { return _updateInterval; } set { _updateInterval = value; } }
            //public float DecayRatio { get { return _decay; } set { _decay = value; } }
            //public float AttackRatio { get { return _attack; } set { _attack = value; } }
            //public int HistoryDuration { get { return _historyDuration; } set { _historyDuration = value; } }
            //public int IgnoreHistoryDuration { get { return _ignoreHistoryDuration; } set { _ignoreHistoryDuration = value; } }
            //public bool Shadow { get { return _shadow; } set { _shadow = value; } }
            //public bool ShowHistory { get { return _showHistory; } set { _showHistory = value; } }
            //public System.Drawing.Color HistoryColor { get { return _historyColor; } set { _historyColor = value; } }
            //public bool PeakHold { get { return _peakHold; } set { _peakHold = value; } }
            //public System.Drawing.Color PeakHoldMarkerColor { get { return _peakHoldMarkerColor; } set { _peakHoldMarkerColor = value; } }
            //public System.Drawing.Color LowColor { get { return _lowColor; } set { _lowColor = value; } }
            //public System.Drawing.Color HighColor { get { return _highColor; } set { _highColor = value; } }
            //public System.Drawing.Color TitleColor { get { return _titleColor; } set { _titleColor = value; } }
            //public Reading ReadingSource { get { return _readingSource; } set { _readingSource = value; } }
            //public System.Drawing.Color Colour { get { return _colour; } set { _colour = value; } }
            //public System.Drawing.Color MarkerColour { get { return _markerColour; } set { _markerColour = value; } }
            //public System.Drawing.Color SubMarkerColour { get { return _subMarkerColour; } set { _subMarkerColour = value; } }
            //public bool ShowMarker { get { return _showMarker; } set { _showMarker = value; } }
            //public bool ShowSubMarker { get { return _showSubMarker; } set { _showSubMarker = value; } }
            //public clsBarItem.BarStyle BarStyle { get { return _barStyle; } set { _barStyle = value; } }
            //public string Text1 { get { return _text_1.Replace("|", ""); } set { _text_1 = value.Replace("|", ""); } }
            //public string Text2 { get { return _text_2.Replace("|", ""); } set { _text_2 = value.Replace("|", ""); } }
            //public bool FadeOnRx { get { return _fadeOnRx; } set { _fadeOnRx = value; } }
            //public bool FadeOnTx { get { return _fadeOnTx; } set { _fadeOnTx = value; } }
            //public bool ShowType { get { return _showType; } set { _showType = value; } }
            //public System.Drawing.Color SegmentedSolidLowColour { get { return _segmentedSolidLowColour; } set { _segmentedSolidLowColour = value; } }
            //public System.Drawing.Color SegmentedSolidHighColour { get { return _segmentedSolidHighColour; } set { _segmentedSolidHighColour = value; } }
            //public bool PeakValue { get { return _peakValue; } set { _peakValue = value; } }
            //public System.Drawing.Color PeakValueColour { get { return _peakValueColour; } set { _peakValueColour = value; } }
            //public float EyeScale { get { return _eyeScale; } set { _eyeScale = value; } }
            //public float SpacerPadding { get { return _spacerPadding; } set { _spacerPadding = value; } }
            //public bool BackPanel { get { return _back_panel; } set { _back_panel = value; } }
            //public float EyeBezelScale { get { return _eyeBezelScale; } set { _eyeBezelScale = value; } }
            //public bool Average { get { return _average; } set { _average = value; } }
            //public bool DarkMode { get { return _darkMode; } set { _darkMode = value; } }
            //public float MaxPower { get { return _maxPower; } set { _maxPower = value; } }
            //public System.Drawing.Color PowerScaleColour { get { return _powerScaleColour; } set { _powerScaleColour = value; } }
            //public clsBarItem.Units Unit { get { return _units; } set { _units = value; } }
            //public string FontFamily1 { get { return _font_family_1.Replace("|", "++><++"); } set { _font_family_1 = value.Replace("++><++", "|"); } }
            //public string FontFamily2 { get { return _font_family_2.Replace("|", "++><++"); } set { _font_family_2 = value.Replace("++><++", "|"); } }
            //public FontStyle FontStyle1 { get { return _font_style_1; } set { _font_style_1 = value; } }
            //public FontStyle FontStyle2 { get { return _font_style_2; } set { _font_style_2 = value; } }
            //public float FontSize1 { get { return _font_size_1; } set { _font_size_1 = value; } }
            //public float FontSize2 { get { return _font_size_2; } set { _font_size_2 = value; } }

            public Guid GetMMIOGuid(int index)
            {
                return (Guid)GetSetting($"_mmio_guid_{index}", typeof(Guid));
            }
            public void SetMMIOGuid(int index, Guid g)
            {
                SetSetting($"_mmio_guid_{index}", g);
            }
            public string GetMMIOVariable(int index)
            {
                return (string)GetSetting($"_mmio_variable_{index}", typeof(string));
            }
            public void SetMMIOVariable(int index, string variable)
            {
                SetSetting($"_mmio_variable_{index}", variable);
            }
            //public Guid GetMMIOGuid(int index)
            //{
            //    return _mmio_guid[index];
            //}
            //public void SetMMIOGuid(int index, Guid g)
            //{
            //    _mmio_guid[index] = g;
            //}
            //public string GetMMIOVariable(int index)
            //{
            //    return _mmio_variable[index];
            //}
            //public void SetMMIOVariable(int index, string variable)
            //{
            //    _mmio_variable[index] = variable;
            //}
        }
        
        public static ImageFetcher ImgFetch
        {
            get { return _image_fetcher; }
        }
        public static CustomReadings ReadingsCustom(int rx)
        {
            rx -= 1;
            return _custom_readings[rx];
        }
        // zero reading
        public static void ZeroReading(out float value, int rx, Reading reading)
        {
            value = 0;

            switch (reading)
            {
                case Reading.SIGNAL_STRENGTH:
                case Reading.AVG_SIGNAL_STRENGTH:
                    {
                        if (IsAboveS9Frequency(rx))
                            value = -153; //S0
                        else
                            value = -133; //S0
                    }
                    break;
                case Reading.ADC_PK:
                case Reading.ADC_AV:
                    {
                        value = -120.0f;
                    }
                    break;
                case Reading.AGC_AV:
                case Reading.AGC_PK:
                    {
                        value = -125.0f;
                    }
                    break;
                case Reading.AGC_GAIN:
                    {
                        value = -50.0f;
                    }
                    break;
                case Reading.MIC:
                case Reading.MIC_PK:
                    {
                        value = -120.0f;
                    }
                    break;
                case Reading.LEVELER:
                case Reading.LEVELER_PK:
                    {
                        value = -30.0f;
                    }
                    break;
                case Reading.EQ:
                case Reading.EQ_PK:
                    {
                        value = -30.0f;
                    }
                    break;
                case Reading.LVL_G:
                    {
                        value = 0f;
                    }
                    break;
                case Reading.ALC:
                case Reading.ALC_PK:
                    {
                        value = -120.0f;
                    }
                    break;
                case Reading.ALC_G: //alc comp
                    {
                        value = 0f;
                    }
                    break;
                case Reading.ALC_GROUP:
                    {
                        value = -30.0f;
                    }
                    break;
                case Reading.CFC_AV:
                case Reading.CFC_PK:
                    {
                        value = -30.0f;
                    }
                    break;
                case Reading.CFC_G:
                    {
                        value = 0f;
                    }
                    break;
                case Reading.COMP:
                case Reading.COMP_PK:
                    {
                        value = -30.0f;
                    }
                    break;
                //case Reading.CPDR: //CPDR is the same as comp
                //case Reading.CPDR_PK:
                    //value = -30.0f;
                    //break;
                case Reading.PWR:
                case Reading.REVERSE_PWR:
                    {
                        value = 0f;
                    }
                    break;
                case Reading.SWR:
                    {
                        value = 1.0f;
                    }
                    break;
                case Reading.ESTIMATED_PBSNR:
                    {
                        value = 0f;
                    }
                    break;
                case Reading.VOLTS:
                    {
                        value = 0f;
                    }
                    break;
                case Reading.AMPS:
                    {
                        value = 0f;
                    }
                    break;
            }
            setReadingForced(rx, reading, value);
        }
        //

        //private static object _spectrumArrayLock = new object();
        //public static void ResizeSpectrum(int len)
        //{
        //    lock (_spectrumArrayLock)
        //    {
        //        if (_newSpectrumPassband == null || _newSpectrumPassband.Length != len)
        //        {
        //            _newSpectrumPassband = new float[len];

        //            if (_currentSpectrumPassband == null)
        //            {
        //                _currentSpectrumPassband = new float[len];
        //            }
        //            else
        //            {
        //                float[] tmp = new float[len];
        //                int stop = Math.Min(tmp.Length, _currentSpectrumPassband.Length);

        //                for (int i = 0; i < stop; i++)
        //                {
        //                    tmp[i] = _currentSpectrumPassband[i];
        //                }
        //                for (int i = stop; i < len; i++)
        //                {
        //                    tmp[i] = Display.SpectrumGridMin;
        //                }

        //                _currentSpectrumPassband = tmp;
        //            }
        //        }
        //    }
        //}
        //public static bool SpectrumReady
        //{
        //    get { return _spectrumReady; }
        //    set { _spectrumReady = value; }
        //}
        public static int GetMeterTXRXType(MeterType meter)
        {
            // 0 - rx
            // 1 - tx
            // 2 - other
            switch (meter)
            {
                case MeterType.SIGNAL_STRENGTH: return 0;
                case MeterType.AVG_SIGNAL_STRENGTH: return 0;
                case MeterType.SIGNAL_TEXT: return 0;
                case MeterType.ADC: return 0;
                case MeterType.AGC: return 0;
                case MeterType.AGC_GAIN: return 0;
                case MeterType.ESTIMATED_PBSNR: return 0;

                case MeterType.MIC: return 1;
                case MeterType.PWR: return 1;
                case MeterType.REVERSE_PWR: return 1;
                case MeterType.ALC: return 1;
                case MeterType.EQ: return 1;
                case MeterType.LEVELER: return 1;
                case MeterType.COMP: return 1;
                //case MeterType.CPDR: return 1;//CPDR is the same as comp
                case MeterType.ALC_GAIN: return 1;
                case MeterType.ALC_GROUP: return 1;
                case MeterType.LEVELER_GAIN: return 1;
                case MeterType.CFC: return 1;
                case MeterType.CFC_GAIN: return 1;
                case MeterType.SWR: return 1;

                case MeterType.MAGIC_EYE: return 2;
                case MeterType.ANANMM: return 2;
                case MeterType.CROSS: return 2;
                case MeterType.VFO_DISPLAY: return 2;
                case MeterType.CLOCK: return 2;
                case MeterType.SPACER: return 2;
                case MeterType.TEXT_OVERLAY: return 2;
                case MeterType.DATA_OUT: return 2;
                case MeterType.ROTATOR: return 2;
                case MeterType.LED: return 2;
                case MeterType.WEB_IMAGE: return 2;
                case MeterType.BAND_BUTTONS: return 2;
                case MeterType.MODE_BUTTONS: return 2;
                case MeterType.FILTER_BUTTONS: return 2;
                case MeterType.ANTENNA_BUTTONS: return 2;
                case MeterType.HISTORY: return 2;
                case MeterType.TUNESTEP_BUTTONS: return 2;
                case MeterType.DISCORD_BUTTONS: return 2;
                    //case MeterType.SPECTRUM: return 2;
            }

            return 0;
        }
        public static string MeterName(MeterType meter)
        {
            switch (meter)
            {
                case MeterType.SIGNAL_STRENGTH: return "Signal Strength";
                case MeterType.AVG_SIGNAL_STRENGTH: return "Avg Signal Strength";
                case MeterType.SIGNAL_TEXT: return "Signal Strength Text";
                case MeterType.ADC: return "ADC";
                case MeterType.AGC: return "AGC";
                case MeterType.AGC_GAIN: return "AGC Gain";
                case MeterType.MIC: return "Mic";
                case MeterType.PWR: return "ForwardPower";
                case MeterType.REVERSE_PWR: return "Reverse Power";
                case MeterType.ALC: return "ALC";
                case MeterType.EQ: return "EQ";
                case MeterType.LEVELER: return "Leveler";
                case MeterType.COMP: return "Compression";
                //case MeterType.CPDR: return "Compander";//CPDR is the same as comp
                case MeterType.ALC_GAIN: return "ALC Compression";
                case MeterType.ALC_GROUP: return "ALC Group";
                case MeterType.LEVELER_GAIN: return "Leveler Gain";
                case MeterType.CFC: return "CFC Compression";
                case MeterType.CFC_GAIN: return "CFC Compression Gain";
                case MeterType.MAGIC_EYE: return "Magic Eye";
                case MeterType.ESTIMATED_PBSNR: return "Estimated PBSNR";
                case MeterType.ANANMM: return "Anan Multi Meter";
                case MeterType.CROSS: return "Cross Meter";
                case MeterType.SWR: return "SWR";
                case MeterType.SPACER: return "Spacer";
                case MeterType.TEXT_OVERLAY: return "Text Overlay";
                case MeterType.LED: return "Led Indicator";
                case MeterType.WEB_IMAGE: return "Web Image";
                case MeterType.BAND_BUTTONS: return "Band Buttons";
                case MeterType.DISCORD_BUTTONS: return "Discord Buttons";
                case MeterType.MODE_BUTTONS: return "Mode Buttons";
                case MeterType.FILTER_BUTTONS: return "Filter Buttons";
                case MeterType.ANTENNA_BUTTONS: return "Antenna Buttons";
                case MeterType.DATA_OUT: return "Data Out Node";
                case MeterType.ROTATOR: return "Rotator";
                case MeterType.VFO_DISPLAY: return "Vfo Display";
                case MeterType.CLOCK: return "Clock";
                case MeterType.HISTORY: return "History Graph";
                case MeterType.TUNESTEP_BUTTONS: return "Tunestep Buttons";
            }

            return meter.ToString();
        }
        public static string ReadingName(Reading reading)
        {
            switch (reading)
            {
                case Reading.ADC_AV: return "ADC Average";
                case Reading.ADC_PK: return "ADC Peak";
                case Reading.AGC_AV: return "AGC Average";
                case Reading.AGC_PK: return "AGC Peak";
                case Reading.AGC_GAIN: return "AGC Gain";
                //case Reading.EYE_PERCENT: return "Magic Eye";
                case Reading.ALC: return "ALC";
                case Reading.ALC_G: return "ALC Compression";
                case Reading.ALC_GROUP: return "ALC Group";
                case Reading.ESTIMATED_PBSNR: return "Estimated PBSNR";
                case Reading.ALC_PK: return "ALC (pk)";// Peak";
                case Reading.AMPS: return "Amps";
                case Reading.AVG_SIGNAL_STRENGTH: return "Signal Average";
                case Reading.CAL_FWD_PWR: return "Calibrated FWD Power";
                case Reading.CFC_G: return "CFC Compression";
                case Reading.CFC_PK: return "CFC Compression (pk)";// Peak";
                case Reading.CFC_AV: return "CFC Compression Average";
                case Reading.COMP: return "Compression";
                case Reading.COMP_PK: return "Compression (pk)";// Peak";
                //case Reading.CPDR: return "Compander"; //CPDR is the same as comp
                //case Reading.CPDR_PK: return "Compander Peak"; //CPDR is the same as comp
                case Reading.DRIVE_FWD_ADC: return "Drive Forward ADC";
                case Reading.DRIVE_PWR: return "Drive Power";
                case Reading.EQ: return "EQ";
                case Reading.EQ_PK: return "EQ (pk)";// Peak";
                case Reading.FWD_ADC: return "Forward ADC";
                case Reading.FWD_VOLT: return "Forward Volt";
                case Reading.LEVELER: return "Leveler";
                case Reading.LEVELER_PK: return "Leveler (pk)";// Peak";
                case Reading.LVL_G: return "Leveler Gain";
                case Reading.MIC: return "MIC";
                case Reading.MIC_PK: return "MIC (pk)";// Peak";
                case Reading.PA_FWD_PWR: return "PA Forward Power";
                case Reading.PA_REV_PWR: return "PA Reverse Power";
                case Reading.PWR: return "Power";
                case Reading.REVERSE_PWR: return "Reverse Power";
                case Reading.REV_ADC: return "Reverse ADC";
                case Reading.REV_VOLT: return "Reverse VOLT";
                case Reading.SIGNAL_STRENGTH: return "Signal";
                case Reading.SWR: return "SWR";
                case Reading.VOLTS: return "Volts";
                case Reading.AZ: return "Azimuth";
                case Reading.ELE: return "Elevation";

                // not used
                case (Reading)(int)22:return "";
                case (Reading)(int)23: return "";
            }

            return reading.ToString();
        }
        public static string ReadingUnits(Reading reading)
        {
            switch (reading)
            {
                case Reading.ADC_AV: return "dBFS";
                case Reading.ADC_PK: return "dBFS";
                case Reading.AGC_PK: return "dB";
                case Reading.AGC_AV: return "dB";
                case Reading.AGC_GAIN: return "dB";
                //case Reading.EYE_PERCENT: return "?";
                case Reading.ALC: return "dB";
                case Reading.ALC_G: return "dB";
                case Reading.ALC_GROUP: return "dB";
                case Reading.ESTIMATED_PBSNR: return "dB";
                case Reading.ALC_PK: return "dB";
                case Reading.AMPS: return "A";
                case Reading.AVG_SIGNAL_STRENGTH: return "dBm";
                case Reading.CAL_FWD_PWR: return "W";
                case Reading.CFC_G: return "dB";
                case Reading.CFC_PK: return "dB";
                case Reading.CFC_AV: return "dB";
                case Reading.COMP: return "dB";
                case Reading.COMP_PK: return "dB";
                //case Reading.CPDR: return "dB"; //CPDR is the same as comp
                //case Reading.CPDR_PK: return "dB"; //CPDR is the same as comp
                case Reading.DRIVE_FWD_ADC: return "?";
                case Reading.DRIVE_PWR: return "W";
                case Reading.EQ: return "dB";
                case Reading.EQ_PK: return "dB";
                case Reading.FWD_ADC: return "FWD ADC";
                case Reading.FWD_VOLT: return "?";
                case Reading.LEVELER: return "dB";
                case Reading.LEVELER_PK: return "dB";
                case Reading.LVL_G: return "dB";
                case Reading.MIC: return "dB";
                case Reading.MIC_PK: return "dB";
                case Reading.PA_FWD_PWR: return "W";
                case Reading.PA_REV_PWR: return "W";
                case Reading.PWR: return "W";
                case Reading.REVERSE_PWR: return "W";
                case Reading.REV_ADC: return "?";
                case Reading.REV_VOLT: return "V";
                case Reading.SIGNAL_STRENGTH: return "dBm";
                case Reading.SWR: return ":1";
                case Reading.VOLTS: return "V";
                case Reading.AZ: return "°";
                case Reading.ELE: return "°";
            }

            return reading.ToString();
        }

        private static void UpdateMeters()
        {
            List<Reading> readingsUsed = new List<Reading>();

            _meterThreadRunning = true;
            while (_meterThreadRunning)
            {
                int nDelay = int.MaxValue;
                lock (_metersLock)
                {
                    // udpate any meter
                    foreach (KeyValuePair<string, clsMeter> kvp in _meters)
                    {
                        clsMeter m = kvp.Value;

                        if (m.Enabled)
                        {
                            readingsUsed.Clear();

                            m.Update(ref readingsUsed);

                            int nTmp = m.DelayForUpdate();
                            if (nTmp < nDelay) nDelay = nTmp;

                            //lock (_readingsLock)
                            {
                                // use/invalidate any readings that have been used, so that they can be updated
                                foreach (Reading rt in readingsUsed)
                                {
                                    _readings[m.RX].UseReading(rt);
                                }
                            }
                        }
                    }
                }
                if (nDelay == int.MaxValue) nDelay = 250;
                Thread.Sleep(nDelay);
            }
        }

        //images, used by dxrenderer
        private static void loadImages()
        {
            string sDefaultPath = _openHPSDR_appdatapath;
            string sSkinPath = _current_skin_path;

            string[] imageFileNames = { "ananMM", "ananMM-bg", "ananMM-bg-tx", "cross-needle", "cross-needle-bg", "eye-bezel", "rotator_az-bg", "rotator_ele-bg", "rotator_both-bg", "rotator_map-bg" };
            string[] imageFileNameParts = { "", "-small", "-large", "-dark", "-dark-small", "-dark-large" };
            string[] image_extensions = { ".png", ".jpg", ".jpeg", ".bmp" };

            // load
            if (!sDefaultPath.EndsWith("\\")) sDefaultPath += "\\";
            if (System.IO.Directory.Exists(sDefaultPath))
            {
                for (int n = 0; n < imageFileNames.Length; n++)
                {
                    string sSkinFileName = sSkinPath + "\\Meters\\" + imageFileNames[n];
                    string sDefaultFileName = sDefaultPath + "\\Meters\\" + imageFileNames[n];
                    for (int i = 0; i < imageFileNameParts.Length; i++)
                    {
                        for (int nn = 0; nn < image_extensions.Length; nn++)
                        {
                            string image_filname = imageFileNameParts[i] + image_extensions[nn];
                            if (File.Exists(sSkinFileName + image_filname))
                            {
                                // remove this as it is a meter skin
                                string sRemove = imageFileNames[n] + imageFileNameParts[i];
                                removeImageCacheData(sRemove);
                                foreach (KeyValuePair<string, DXRenderer> kvp in _DXrenderers)
                                {
                                    DXRenderer r = kvp.Value;
                                    r.RemoveDXImage(sRemove);
                                }

                                loadImage(sSkinFileName + image_filname, true);
                                break;
                            }
                            else if (File.Exists(sDefaultFileName + image_filname))
                            {
                                loadImage(sDefaultFileName + image_filname, false);
                                break;
                            }
                        }
                    }
                }
            }
        }
        private static void loadImage(string sFilePath, bool isSkinImage)
        {
            string sID = System.IO.Path.GetFileNameWithoutExtension(sFilePath);

            if (!MeterManager.ContainsBitmap(sID)) // check contains incase of filename dupe somehow
            {
                if (System.IO.File.Exists(sFilePath))
                {
                    System.Drawing.Image image = null;
                    try
                    {
                        image = System.Drawing.Image.FromFile(sFilePath);
                        System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(image);
                        bmp.Tag = isSkinImage;
                        addBitmap(sID, bmp);
                    }
                    catch { }
                    finally
                    {
                        if (image != null) image.Dispose();
                    }
                }
            }
        }
        private static void loadResouceImages()
        {
            Dictionary<string, System.Drawing.Bitmap> resource_bitmaps = new Dictionary<string, System.Drawing.Bitmap>();

            // the resource images to use
            resource_bitmaps.Add("vfo_lock", Properties.Resources.Lock_64);
            resource_bitmaps.Add("vfo_sync", Properties.Resources.Link_64);

            foreach (KeyValuePair<string, System.Drawing.Bitmap> kvp in resource_bitmaps)
            {
                if (!ContainsBitmap(kvp.Key)) // check contains incase of filename dupe somehow
                {
                    System.Drawing.Image image = null;
                    try
                    {
                        image = kvp.Value;
                        System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(image);
                        bmp.Tag = false;
                        addBitmap(kvp.Key, bmp);
                    }
                    catch { }
                    finally
                    {
                        if (image != null) image.Dispose();
                    }
                }
            }
        }
        private static void clearAllCachedImageData(bool bOnlySkins = false)
        {
            if (_image_cache == null) return;

            lock (_imageLock)
            {
                List<string> keys = _image_cache.Keys.ToList();

                foreach(string sKey in keys)
                {
                    removeImageCacheData(sKey, bOnlySkins);
                }

                if (!bOnlySkins)
                {
                    _image_cache.Clear();
                    _image_streamdata_cache.Clear();
                }
            }
        }
        private static bool removeImageCacheData(string sKey, bool bOnlySkins = false)
        {
            lock (_imageLock)
            {
                if (_image_cache == null) return false;
                if (!_image_cache.ContainsKey(sKey)) return false;

                // remove the bitmap, tag = true then skin image
                if (bOnlySkins && !(bool)_image_cache[sKey].Tag) return false;

                _image_cache[sKey].Dispose();
                _image_cache.Remove(sKey);

                if (_image_streamdata_cache != null)
                {
                    if(_image_streamdata_cache.ContainsKey(sKey))
                    {
                        // remove the stream related to the bitmap
                        _image_streamdata_cache[sKey].Dispose();
                        _image_streamdata_cache.Remove(sKey);
                    }
                }
            }
            return true;
        }
        private static bool addBitmap(string sKey, System.Drawing.Bitmap image)
        {
            bool bRet = true;
            lock (_imageLock)
            {
                if (_image_cache == null) bRet = false;
                if (_image_cache.ContainsKey(sKey)) bRet = false;

                _image_cache.Add(sKey, image);
            }
            return bRet;
        }
        internal static System.Drawing.Bitmap GetBitmap(string sKey)
        {
            lock (_imageLock)
            {
                if (_image_cache == null) return null;
                if (!_image_cache.ContainsKey(sKey)) return null;

                return _image_cache[sKey];
            }
        }
        internal static bool ContainsBitmap(string sKey)
        {
            lock (_imageLock)
            {
                if (_image_cache == null) return false;
                return _image_cache.ContainsKey(sKey);
            }
        }
        internal static void AddStreamData(string sId, MemoryStream tempStream)
        {
            lock (_imageLock)
            {
                if (_image_streamdata_cache == null) return;
                if (_image_streamdata_cache.ContainsKey(sId)) return;

                _image_streamdata_cache.Add(sId, tempStream);
            }
        }
        internal static MemoryStream GetStreamData(string sKey)
        {
            lock (_imageLock)
            {
                if (_image_streamdata_cache == null) return null;
                if (!_image_streamdata_cache.ContainsKey(sKey)) return null;

                return _image_streamdata_cache[sKey];
            }
        }
        internal static bool ContainsStreamData(string sKey)
        {
            lock (_imageLock)
            {
                if (_image_streamdata_cache == null) return false;
                return _image_streamdata_cache.ContainsKey(sKey);
            }
        }
        internal static void RemoveStreamData(string sKey)
        {
            lock (_imageLock)
            {
                if (_image_streamdata_cache == null) return;
                if (_image_streamdata_cache.ContainsKey(sKey))
                {
                    MemoryStream ms = _image_streamdata_cache[sKey];
                    
                    Utilities.Dispose(ref ms);
                    ms = null;

                    _image_streamdata_cache.Remove(sKey);
                }
            }
        }
        //

        public static void ContainerBorder(string sId, bool border)
        {
            lock (_metersLock) {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];
                uc.UCBorder = border;
            }
        }
        public static void NoTitle(string sId, bool noTitle)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];
                uc.NoControls = noTitle;
            }
        }
        public static void AutoContainerHeight(string sId, bool auto_height)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];
                uc.AutoHeight = auto_height;
            }
        }
        public static void SetContainerHeight(string sId, int height)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];

                if (uc.IsHandleCreated)
                {
                    uc.BeginInvoke(new MethodInvoker(() =>
                    {
                        uc.ChangeHeight(height);
                    }));
                }
            }
        }
        public static void ContainerMinimises(string sId, bool minimises)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];

                if (_lstMeterDisplayForms.ContainsKey(uc.ID))
                {
                    frmMeterDisplay f = _lstMeterDisplayForms[uc.ID];

                    if(minimises != uc.ContainerMinimises)
                    {
                        uc.ContainerMinimises = minimises;
                        f.ContainerMinimises = minimises;
                    }
                }
            }
        }
        public static void LockContainer(string sId, bool locked)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null || !_lstUCMeters.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];
                uc.Locked = locked;
            }
        }
        public static void ShowContainerOnRX(string sId, bool visible)
        {
            lock (_metersLock)
            {
                if (_meters == null || !_meters.ContainsKey(sId)) return;
                if (_lstUCMeters == null || !_lstUCMeters.ContainsKey(sId)) return;

                clsMeter m = _meters[sId];
                m.ShowOnRX = visible;

                ucMeter uc = _lstUCMeters[sId];
                uc.ShowOnRX = visible;

                if (!m.MOX)
                {
                    enableContainer(sId, visible);
                }
            }
        }
        public static void ShowContainerOnTX(string sId, bool visible)
        {
            lock (_metersLock)
            {
                if (_meters == null || !_meters.ContainsKey(sId)) return;
                if (_lstUCMeters == null || !_lstUCMeters.ContainsKey(sId)) return;

                clsMeter m = _meters[sId];
                m.ShowOnTX = visible;

                ucMeter uc = _lstUCMeters[sId];
                uc.ShowOnTX = visible;

                if (m.MOX)
                {
                    enableContainer(sId, visible);
                }
            }
        }
        private static void enableContainer(string sId, bool enabled)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;
                
                ucMeter uc = _lstUCMeters[sId];
                bool bOldState = uc.MeterEnabled;
                uc.MeterEnabled = enabled;

                if (enabled != bOldState && _lstMeterDisplayForms.ContainsKey(uc.ID))
                {
                    frmMeterDisplay f = _lstMeterDisplayForms[uc.ID];
                    f.FormEnabled = enabled;

                    if (uc.Floating)
                    {
                        if (!enabled)
                            f.Hide();
                        else
                            setMeterFloating(uc, f);
                    }
                    else
                    {
                        if (!enabled)
                        {
                            uc.Hide();
                            uc.Repaint();
                        }
                        else
                            returnMeterFromFloating(uc, f);
                    }

                    // meter
                    if(_meters != null && _meters.ContainsKey(sId))
                        _meters[sId].Enabled = enabled;

                    // DXrenderer
                    if (_DXrenderers != null && _DXrenderers.ContainsKey(uc.ID))
                        _DXrenderers[sId].Enabled = enabled;
                }
            }
        }
        public static bool ContainerHasBorder(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.UCBorder;
            }
        }
        public static bool ContainerNoTitleBar(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.NoControls;
            }
        }
        public static bool ContainerAutoHeight(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.AutoHeight;
            }
        }
        public static bool ContainerLocked(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.Locked;
            }
        }
        public static bool ContainerShowOnRX(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.ShowOnRX;
            }
        }
        public static bool ContainerShowOnTX(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.ShowOnTX;
            }
        }
        public static bool ContainerMinimises(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                return uc.ContainerMinimises;
            }
        }
        public static void ContainerNotes(string sId, string notes)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;
                if (!_DXrenderers.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];
                uc.Notes = notes;
            }
        }
        public static string GetContainerNotes(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return "";
                if (!_lstUCMeters.ContainsKey(sId)) return "";

                ucMeter uc = _lstUCMeters[sId];
                return uc.Notes;
            }
        }
        public static void ContainerBackgroundColour(string sId, System.Drawing.Color c)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return;
                if (!_lstUCMeters.ContainsKey(sId)) return;
                if (!_DXrenderers.ContainsKey(sId)) return;

                ucMeter uc = _lstUCMeters[sId];
                uc.BackColor = c;
                DXRenderer r = _DXrenderers[sId];
                r.BackgroundColour = c;
            }
        }
        public static System.Drawing.Color GetContainerBackgroundColour(string sId)
        {
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return System.Drawing.Color.Empty;
                if (!_lstUCMeters.ContainsKey(sId)) return System.Drawing.Color.Empty;

                ucMeter uc = _lstUCMeters[sId];
                return uc.BackColor;
            }
        }
        public static void HighlightContainer(string sId)
        {
            if (_DXrenderers == null) return;

            foreach (KeyValuePair<string, DXRenderer> kvp in _DXrenderers)
            {
                kvp.Value.HighlightEdge = false;
            }

            if (_DXrenderers.ContainsKey(sId))
            {
                _DXrenderers[sId].HighlightEdge = true;
            }
        }
        public static void DisposeImageData()
        {
            foreach (KeyValuePair<string, MemoryStream> kvp in _image_streamdata_cache)
            {
                MemoryStream tempStream = kvp.Value;
                Utilities.Dispose(ref tempStream);
                tempStream = null;
            }
            _image_streamdata_cache.Clear();

            foreach (KeyValuePair<string, System.Drawing.Bitmap> kvp in _image_cache)
            {
                System.Drawing.Bitmap tempBmp = kvp.Value;
                tempBmp.Dispose();
            }
            _image_cache.Clear();
        }
        public static void SetAntennaAuxText(string n1, string n2, string n3)
        {
            if (n1 != _ant_aux_names[0] || n2 != _ant_aux_names[1] || n3 != _ant_aux_names[2])
            {
                _ant_aux_names[0] = n1;
                _ant_aux_names[1] = n2;
                _ant_aux_names[2] = n3;
                updateAntennaMeters();
            }
        }
        private static void initAntennaArrays()
        {
            // similar to setup.cs InitAlexAntTables() but own local copy of the state, updated by the delegates
            _ant_no_tx = new bool[3];
            _ant_aux_names = new string[3];
            _ant_no_tx[0] = false; // always
            _ant_no_tx[1] = false;
            _ant_no_tx[2] = false;
            _ant_aux_names[0] = "";
            _ant_aux_names[1] = "";
            _ant_aux_names[2] = "";

            _rx_ant_aux = new[] { new bool[]  { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false }
                                                     };


            _rx_ant = new[] { new bool[]  { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false }
                                                     };

            _tx_ant = new[] { new bool[]  { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false },
                                                            new bool[] { false, false, false }
                                                     };
        }
        public static void Init(Console c)
        {
            _console = c;
            _image_fetcher.Version = c.ProductVersion;
            //_power = _console.PowerOn;
            _rx1VHForAbove = _console.VFOAFreq >= _console.S9Frequency;
            _rx2VHForAbove = _console.RX2Enabled && _console.VFOBFreq >= _console.S9Frequency;
            _currentHPSDRmodel = _console.CurrentHPSDRModel;
            _apolloPresent = _console.ApolloPresent;
            _alexPresent = _console.AlexPresent;
            _paPresent = _console.PAPresent;
            _transverterIndex = _console.TXXVTRIndex;

            initAntennaArrays();
            addDelegates();

            _meterThread = new Thread(new ThreadStart(UpdateMeters))
            {
                Name = "Multimeter Thread",
                Priority = ThreadPriority.Lowest,
                IsBackground = true
            };
            _meterThread.Start();
        }
        private static Dictionary<string, DXRenderer> _DXrenderers = new Dictionary<string, DXRenderer>();
        private static void addRenderer(string sId, int rx, PictureBox target, clsMeter meter, System.Drawing.Color backColour)
        {
            DXRenderer renderer = new DXRenderer(sId, rx, target, _console, meter);
            renderer.BackgroundColour = backColour;

            _DXrenderers.Add(sId, renderer);
        }
        public static void UpdateS9()
        {
            _rx1VHForAbove = _console.VFOAFreq >= _console.S9Frequency;
            _rx2VHForAbove = _console.RX2Enabled && _console.VFOBFreq >= _console.S9Frequency;
            zeroAllMeters();
        }
        public static void RefreshAllImages()
        {
            if (_DXrenderers.Count < 1) return;

            //remove skins from cache
            clearAllCachedImageData();

            foreach (KeyValuePair<string, DXRenderer> kvp in _DXrenderers)
            {
                DXRenderer r = kvp.Value;
                r.RemoveAllDXImages();
            }

            loadImages();
        }
        private static void loadDXSkinImages()
        {
            if (_DXrenderers.Count < 1) return;

            //remove skins from cache
            clearAllCachedImageData(true);

            foreach (KeyValuePair<string, DXRenderer> kvp in _DXrenderers)
            {
                DXRenderer r = kvp.Value;
                r.RemoveAnySkinImages();              
            }

            loadImages();
        }
        public static void RunRendererDisplay(string sId)
        {
            if (!_DXrenderers.ContainsKey(sId)) return;

            DXRenderer r = _DXrenderers[sId];

            r.RunDisplay(); // causes dx to initialise

            loadImages();
            loadResouceImages();
        }
        public static void RunAllRendererDisplays()
        {
            if (_DXrenderers.Count < 1) return;

            foreach(KeyValuePair <string, DXRenderer> kvp in _DXrenderers)
            {
                RunRendererDisplay(kvp.Key);
            }
        }
        public static void SetVsync(bool vsync)
        {
            if (_DXrenderers.Count < 1) return;

            foreach (KeyValuePair<string, DXRenderer> kvp in _DXrenderers)
            {
                kvp.Value.SetVsync = vsync;
            }
        }

        public static bool AlwaysUpdateSkin { get; set; }
        public static string CurrentSkin
        {
            get { return _current_skin; }
            set
            {
                _current_skin_path = _openHPSDR_appdatapath + "\\Skins\\" + value;

                if (value != _current_skin || AlwaysUpdateSkin)
                {
                    loadDXSkinImages();
                    _current_skin = value;
                }
            }
        }
        private static void removeRenderer(string sId)
        {
            if (!_DXrenderers.ContainsKey(sId)) return;

            DXRenderer r = _DXrenderers[sId];
            r.ShutdownDX();

            _DXrenderers.Remove(sId);
        }
        public static List<string> GetMeterIDsFromSaveData(Dictionary<string, string> data, bool include_ig_ids = false)
        {
            List<string> ids = new List<string>();
            if (data == null || data.Count == 0) return ids;

            foreach (KeyValuePair<string, string> kvp in data)
            {
                string key = kvp.Key;
                string value = kvp.Value;

                if (key.Contains("meterContData_"))
                {
                    string guid = key.Split('_')[1];
                    if (!ids.Contains(guid)) ids.Add(guid);
                }
                else if (key.Contains("meterData_"))
                {
                    string guid = key.Split('_')[1];
                    if (!ids.Contains(guid)) ids.Add(guid);
                }
                else if (key.Contains("meterIGData_"))
                {
                    string igguid = key.Split('_')[1];
                    if (include_ig_ids && !ids.Contains(igguid)) ids.Add(igguid);

                    string parent_guid = value.Split('|')[1]; // 2nd value in the | sep data
                    if (!ids.Contains(parent_guid)) ids.Add(parent_guid);
                }
                else if (key.Contains("meterIGSettings_")) // or _2_
                {
                    // find container that has this ig setting, it will be any container that has item group with this id
                    string[] split = key.Split('_');
                    string guid;
                    if(split.Length == 3) // for _2_
                    {
                        guid = key.Split('_')[2];
                    }
                    else
                    {
                        guid = key.Split('_')[1];
                    }
                    if (include_ig_ids && !ids.Contains(guid)) ids.Add(guid);

                    lock (_metersLock)
                    {
                        foreach (KeyValuePair<string, clsMeter> ms in _meters)
                        {
                            clsMeter m = ms.Value;
                            Dictionary<string, clsItemGroup> igs = m.getMeterGroups();
                            foreach(KeyValuePair<string, clsItemGroup> kvpig in igs)
                            {
                                if (guid == kvpig.Key)
                                {
                                    if (!ids.Contains(m.ID)) ids.Add(m.ID);
                                }
                            }
                        }
                    }
                }
            }
            return ids;
        }       
        public static void Shutdown()
        {
            if (_image_fetcher != null)
                _image_fetcher.Shutdown();                
                
            MultiMeterIO.StopConnections();

            removeDelegates();

            foreach (KeyValuePair<string, DXRenderer> kvp in _DXrenderers)
            {
                kvp.Value.ShutdownDX();
            }

            DisposeImageData();

            _meterThreadRunning = false;
            if (_meterThread != null && _meterThread.IsAlive)
            {
                int nWait = 0;
                lock (_metersLock)
                {
                    foreach (KeyValuePair<string, clsMeter> kvp in _meters)
                    {
                        clsMeter m = kvp.Value;

                        int nTmp = m.DelayForUpdate();
                        if (nTmp > nWait) nWait = nTmp;
                    }
                }
                _meterThread.Join(nWait + 100); // slightly longer
            }

            foreach (KeyValuePair<string, frmMeterDisplay> kvp in _lstMeterDisplayForms)
            {
                kvp.Value.Close();
            }
        }
        private static void addDelegates()
        {
            if (_delegatesAdded || _console == null) return;

            _console.MeterReadingsChangedHandlers += OnMeterReadings;
            _console.MoxPreChangeHandlers += OnPreMox;
            _console.MoxChangeHandlers += OnMox;
            _console.PowerChangeHanders += OnPower;
            _console.VFOAFrequencyChangeHandlers += OnVFOA;
            _console.VFOBFrequencyChangeHandlers += OnVFOB;
            _console.BandChangeHandlers += OnBandChange;
            _console.BandPreChangeHandlers += OnPreBandChange;
            _console.ModeChangeHandlers += OnModeChangeHandler;
            _console.SplitChangedHandlers += OnSplitChanged;
            _console.VFOTXChangedHandlers += OnVFOTXChanged;
            _console.TXBandChangeHandlers += OnTXBandChanged;
            _console.FilterChangedHandlers += OnFilterChanged;
            _console.MultiRxHandlers += OnMultiRxChanged;
            _console.VFOASubFrequencyChangeHandlers += OnVFOASub;

            _console.AlexPresentChangedHandlers += OnAlexPresentChanged;
            _console.PAPresentChangedHandlers += OnPAPresentChanged;
            _console.ApolloPresentChangedHandlers += OnApolloPresentChanged;
            _console.CurrentModelChangedHandlers += OnCurrentModelChanged;
            _console.TransverterIndexChangedHandlers += OnTransverterIndexChanged;

            _console.RX2EnabledChangedHandlers += OnRX2EnabledChanged;
            _console.RX2EnabledPreChangedHandlers += OnRX2EnabledPreChanged;

            _console.EQChangedHandlers += OnEQChanged;
            _console.LevelerChangedHandlers += OnLevelerChanged;
            _console.CFCChangedHandlers += OnCFCChanged;
            _console.CompandChangedHandlers += OnCompandChanged;

            _console.QuickSplitChangedHandlers += OnQuickSplitChanged;

            _console.BandPanelChangeHandlers += OnBandPanelChanged;
            _console.VHFDetailsChangedHandlers += OnVHFDetailsChanged;
            //_console.FilterNameChangedHandlers += OnFilterNameChanged;

            _console.AntennaRXChangedHandlers += OnAntennaRXChanged;
            _console.AntennaTXChangedHandlers += OnAntennaTXChanged;
            _console.AntennaAuxChangedHandlers += OnAntennaAuxChanged;
            _console.AntennaDoNotTXHandlers += OnAntennaDoNotTX;
            _console.AntennaRxTxHandlers += OnAntennaRxTx;

            _console.TXFrequncyChangedHandlers += OnTXFrequencyChanged;

            _console.VfoALockChangedHandlers += OnVFOaLockChanged;
            _console.VfoBLockChangedHandlers += OnVFObLockChanged;

            _console.VFOSyncChangedHandlers += OnVFOSyncChanged;

            _console.TuneStepIndexChangedHandlers += OnTuneStepIndexChanged;

            _delegatesAdded = true;
        }
        private static void removeDelegates()
        {
            if (!_delegatesAdded || _console == null) return;

            _console.MeterReadingsChangedHandlers -= OnMeterReadings;
            _console.MoxPreChangeHandlers -= OnPreMox;
            _console.MoxChangeHandlers -= OnMox;
            _console.PowerChangeHanders -= OnPower;
            _console.VFOAFrequencyChangeHandlers -= OnVFOA;
            _console.VFOBFrequencyChangeHandlers -= OnVFOB;
            _console.BandChangeHandlers -= OnBandChange;
            _console.BandPreChangeHandlers -= OnPreBandChange;
            _console.ModeChangeHandlers -= OnModeChangeHandler;
            _console.SplitChangedHandlers -= OnSplitChanged;
            _console.VFOTXChangedHandlers -= OnVFOTXChanged;
            _console.TXBandChangeHandlers -= OnTXBandChanged;
            _console.FilterChangedHandlers -= OnFilterChanged;
            _console.MultiRxHandlers -= OnMultiRxChanged;
            _console.VFOASubFrequencyChangeHandlers -= OnVFOASub;

            _console.AlexPresentChangedHandlers -= OnAlexPresentChanged;
            _console.PAPresentChangedHandlers -= OnPAPresentChanged;
            _console.ApolloPresentChangedHandlers -= OnApolloPresentChanged;
            _console.CurrentModelChangedHandlers -= OnCurrentModelChanged;
            _console.TransverterIndexChangedHandlers -= OnTransverterIndexChanged;

            _console.RX2EnabledChangedHandlers -= OnRX2EnabledChanged;
            _console.RX2EnabledPreChangedHandlers -= OnRX2EnabledPreChanged;

            _console.EQChangedHandlers -= OnEQChanged;
            _console.LevelerChangedHandlers -= OnLevelerChanged;
            _console.CFCChangedHandlers -= OnCFCChanged;
            _console.CompandChangedHandlers -= OnCompandChanged;

            _console.QuickSplitChangedHandlers -= OnQuickSplitChanged;

            _console.BandPanelChangeHandlers -= OnBandPanelChanged;
            _console.VHFDetailsChangedHandlers -= OnVHFDetailsChanged;
            //_console.FilterNameChangedHandlers -= OnFilterNameChanged;

            _console.AntennaRXChangedHandlers -= OnAntennaRXChanged;
            _console.AntennaTXChangedHandlers -= OnAntennaTXChanged;
            _console.AntennaAuxChangedHandlers -= OnAntennaAuxChanged;
            _console.AntennaDoNotTXHandlers -= OnAntennaDoNotTX;
            _console.AntennaRxTxHandlers -= OnAntennaRxTx;

            _console.TXFrequncyChangedHandlers -= OnTXFrequencyChanged;

            _console.VfoALockChangedHandlers += OnVFOaLockChanged;
            _console.VfoBLockChangedHandlers += OnVFObLockChanged;

            _console.VFOSyncChangedHandlers -= OnVFOSyncChanged;

            _console.TuneStepIndexChangedHandlers -= OnTuneStepIndexChanged;

            foreach (KeyValuePair<string, ucMeter> kvp in _lstUCMeters)
            {
                kvp.Value.RemoveDelegates();
            }

            _delegatesAdded = false;
        }
        private static void OnTuneStepIndexChanged(int rx, int old_index, int new_index)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    m.TuneStepIndex = new_index;
                    m.TuneStepIndexChanged(old_index, new_index);
                }
            }
        }
        private static void OnVFOSyncChanged(int rx, bool old_state, bool new_state)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    m.VFOSync = new_state;
                }
            }
        }
        private static void OnVFOaLockChanged(int rx, bool old_state, bool new_state)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    m.VFOALock = new_state;
                }
            }
        }
        private static void OnVFObLockChanged(int rx, bool old_state, bool new_state)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    m.VFOBLock = new_state;
                }
            }
        }
        private static readonly object _antenna_lock = new object();
        private static void updateAntennaMeters()
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters)
                {
                    clsMeter m = ms.Value;
                    m.AntennasChanged(Band.FIRST, Band.FIRST, -1, -1);
                }
            }
        }
        private static void OnTXFrequencyChanged(double old_frequency, double new_frequency, Band old_band, Band new_band)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters)
                {
                    clsMeter m = ms.Value;
                    m.AntennasChanged(Band.FIRST, new_band, -1, new_frequency);
                }
            }
        }
        private static void OnAntennaRXChanged(Band b, int antenna, bool old_state, bool new_state)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return;
            if (antenna < 0 || antenna > 2) return;
            //Debug.Print($"====> RX Antenna Changed : band={b.ToString()} button={antenna.ToString()} from={old_state} to={new_state}");
            lock (_antenna_lock)
            {
                _rx_ant[(int)b - (int)Band.B160M][antenna] = new_state;
            }
            if (old_state != new_state)
                updateAntennaMeters();
        }
        private static void OnAntennaTXChanged(Band b, int antenna, bool old_state, bool new_state)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return;
            if (antenna < 0 || antenna > 2) return;
            //Debug.Print($"====> TX Antenna Changed : band={b.ToString()} button={antenna.ToString()} from={old_state} to={new_state}");
            lock (_antenna_lock)
            {
                _tx_ant[(int)b - (int)Band.B160M][antenna] = new_state;
            }
            if (old_state != new_state)
                updateAntennaMeters();
        }
        private static void OnAntennaAuxChanged(Band b, int antenna, bool old_state, bool new_state, string button_text)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return;
            if (antenna < 0 || antenna > 2) return;
            //Debug.Print($"====> Ant Aux Changed : band={b.ToString()} button={antenna.ToString()} from={old_state} to={new_state} buttontext={button_text}");
            lock (_antenna_lock)
            {
                _rx_ant_aux[(int)b - (int)Band.B160M][antenna] = new_state;
            }
            if (old_state != new_state)
                updateAntennaMeters();
        }
        private static void OnAntennaDoNotTX(int antenna, bool old_state, bool new_state)
        {
            if (antenna < 0 || antenna > 2) return;
            //Debug.Print($"====> AntDotNotTX Changed : button={antenna.ToString()} from={old_state} to={new_state}");
            lock (_antenna_lock)
            {
                _ant_no_tx[antenna] = new_state;
            }
            if (old_state != new_state)
                updateAntennaMeters();
        }
        private static void OnAntennaRxTx(bool old_state, bool new_state)
        {
            lock (_metersLock)
            {
                if (old_state != new_state)
                {
                    foreach (KeyValuePair<string, clsMeter> ms in _meters)
                    {
                        clsMeter m = ms.Value;
                        m.AntennasChanged(Band.FIRST, Band.FIRST, -1, -1, new_state ? 2 : 1);
                    }
                }
            }

            if (old_state != new_state)
                updateAntennaMeters();
        }
        public static bool GetNoTX(int antenna)
        {
            if (antenna < 0 || antenna > 2) return false;
            lock (_antenna_lock)
            {
                return _ant_no_tx[antenna];
            }
        }
        public static bool GetRXAntState(Band b, int antenna)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return false;
            if (antenna < 0 || antenna > 2) return false;
            lock (_antenna_lock)
            {                
                return _rx_ant[(int)b - (int)Band.B160M][antenna];
            }
        }
        public static bool GetTXAntState(Band b, int antenna)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return false;
            if (antenna < 0 || antenna > 2) return false;
            lock (_antenna_lock)
            {
                return _tx_ant[(int)b - (int)Band.B160M][antenna];
            }
        }
        public static bool GetRXAuxState(Band b, int antenna)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return false;
            if (antenna < 0 || antenna > 2) return false;
            lock (_antenna_lock)
            {
                return _rx_ant_aux[(int)b - (int)Band.B160M][antenna];
            }
        }
        public static string GetRXAuxName(int antenna)
        {
            if (antenna < 0 || antenna > 2) return "";
            lock (_antenna_lock)
            {
                return _ant_aux_names[antenna];
            }
        }
        public static bool GetTXEnabled(int antenna)
        {
            if (antenna < 0 || antenna > 2) return false;
            lock (_antenna_lock)
            {
                return _ant_no_tx[antenna];
            }
        }
        public static int GetRXAnt(Band b, bool xvtr)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return -2;
            // 0-2 ant ports, 3=byp, 4=ext1, 5=xvtr
            int band_idx = (int)b - (int)Band.B160M;
            if (xvtr)
            {
                if (_rx_ant_aux[band_idx][2])
                {
                    return 5; //xvtr
                }
            }
            else
            {
                if (_rx_ant_aux[band_idx][0])
                {
                    return 3; //bypass
                }
                else if (_rx_ant_aux[band_idx][1])
                {
                    return 4; //xvtr
                }
            }
            for (int n = 0; n < 3; n++)
            {
                if (_rx_ant[band_idx][n]) return n;
            }
            return -1;
        }
        public static int GetTXAnt(Band b)
        {
            if ((int)b < (int)Band.B160M || (int)b > (int)Band.B6M) return -2;
            // 0-2 ant ports
            for (int n = 0; n < 3; n++)
            {
                if (_tx_ant[(int)b - (int)Band.B160M][n]) return n;
            }
            return -1;
        }
        private static void OnSplitChanged(int rx, bool oldSplit, bool newSplit)
        {
            lock (_metersLock)
            {
                bool updateVFOASub = _console.VFOASubInUse; //[2.10.1.0] MW0LGE needed because at init rx2 might not be enabled, and the init function will have been given -999.999 from console.vfoasubfreq
                double freq = _console.VFOASubFreq;

                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;
                    m.Split = rx == m.RX && newSplit;

                    if (rx == m.RX && updateVFOASub) m.VfoSub = freq;
                }
            }
        }
        //private static void OnFilterNameChanged(int rx, Filter f, string old_name, string new_name)
        //{
        //    lock (_metersLock)
        //    {
        //        foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
        //        {
        //            clsMeter m = ms.Value;

        //            if (rx == 1)
        //            {
        //                m.FilterVfoAName = new_name;
        //            }
        //            else
        //            {
        //                m.FilterVfoBName = new_name;
        //            }

        //            if (old_name != new_name)
        //                m.FilterNameDetails(f, new_name);
        //        }
        //    }
        //}
        private static void OnFilterChanged(int rx, Filter oldFilter, Filter newFilter, Band band, int low, int high, string sName)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    bool changed = m.FilterVfoA != newFilter || m.FilterVfoB != newFilter ||
                                   m.FilterVfoAName != sName || m.FilterVfoBName != sName || oldFilter != newFilter;

                    if (changed)
                    {
                        m.FilterVfoA = newFilter;
                        m.FilterVfoB = newFilter;

                        m.FilterVfoAName = sName;
                        m.FilterVfoBName = sName;

                        m.UpdateFilterButtons(newFilter, sName);
                    }
                }
            }
        }
        private static void OnMultiRxChanged(bool newState, bool oldState, double vfoASubFrequency, Band b, bool bRx2Enabled)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters)
                {
                    clsMeter m = ms.Value;
                    m.MultiRxEnabled = newState;
                    m.VfoSub = vfoASubFrequency;
                    m.BandVfoASub = b;
                }
            }
        }

        private static void OnTXBandChanged(Band oldBand, Band newBand, double tx_frequency)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters)
                {
                    clsMeter m = ms.Value;
                    m.TXBand = newBand;
                    m.AntennasChanged(Band.FIRST, newBand, -1, tx_frequency);
                }
            }
        }
        private static void OnVFOTXChanged(bool vfoB, bool oldState, bool newState)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters)
                {
                    clsMeter m = ms.Value;
                    m.TXVFOb = vfoB && newState;

                    if(oldState != newState)
                        m.AntennasChanged(Band.FIRST, Band.FIRST, -1, -1);
                }
            }
        }
        private static void OnModeChangeHandler(int rx, DSPMode oldMode, DSPMode newMode, Band oldBand, Band newBand)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;
                    if (rx == 1)
                    {
                        m.ModeVfoA = newMode;
                        m.ModeVfoB = newMode;
                    }
                    else
                    {
                        m.ModeVfoB = newMode;
                    }

                    if (oldMode != newMode)
                    {
                        m.ModeChanged(oldMode, newMode);
                        m.InitFilterButtons();
                    }
                }
            }
        }       
        private static void OnVHFDetailsChanged(int idx, bool old_state, bool new_state, string old_text, string new_text)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters)
                {
                    clsMeter m = ms.Value;

                    m.VHFDetailsChanged(idx, new_state, new_text);

                    m.ZeroOut(true, false);
                }
            }
        }
        private static void OnBandPanelChanged(int rx, bool gen, bool hf, bool vhf)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    m.BandPanelsChanged(gen, hf, vhf);

                    m.ZeroOut(true, false);
                }
            }
        }
        private static void bandChange(int rx, Band old_band, Band new_band, bool update_button_boxes = false, bool update_band = true)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> ms in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = ms.Value;

                    if (update_band)
                    {
                        if (rx == 1)
                            m.BandVfoA = new_band;
                        else
                            m.BandVfoB = new_band;

                        m.BandChanged(old_band, new_band);

                        m.ZeroOut(true, false);
                    }
                }

                // every meter
                if (update_button_boxes && old_band != new_band)
                {
                    foreach (KeyValuePair<string, clsMeter> ms in _meters)
                    {
                        clsMeter m = ms.Value;                        
                        m.AntennasChanged(new_band, Band.FIRST, -1, -1);
                    }
                }
            }
        }
        private static void OnBandChange(int rx, Band oldBand, Band newBand)
        {
            bandChange(rx, oldBand, newBand);
        }
        private static void OnPreBandChange(int rx, Band currentBand)
        {
            //bandChange(rx, currentBand, currentBand);
        }
        private static void OnTransverterIndexChanged(int oldIndex, int newIndex)
        {
            _transverterIndex = newIndex;
        }
        public static void OnAlexPresentChanged(bool oldSetting, bool newSetting)
        {
            _alexPresent = newSetting;
        }
        public static void OnPAPresentChanged(bool oldSetting, bool newSetting)
        {
            _paPresent = newSetting;
        }
        public static void OnApolloPresentChanged(bool oldSetting, bool newSetting)
        {
            _apolloPresent = newSetting;
        }
        public static void OnCurrentModelChanged(HPSDRModel oldModel, HPSDRModel newModel)
        {
            _currentHPSDRmodel = newModel;
        }
        private static void OnVFOA(Band oldBand, Band newBand, DSPMode oldMode, DSPMode newMode, Filter oldFilter, Filter newFilter, double oldFreq, double newFreq, double oldCentreF, double newCentreF, bool oldCTUN, bool newCTUN, int oldZoomSlider, int newZoomSlider, double offset, int rx)
        {
            if(rx == 1)
                _rx1VHForAbove = newFreq >= _console.S9Frequency;

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = mkvp.Value;
                    m.VfoA = newFreq;
                    m.ModeVfoA = newMode;
                    m.BandVfoA = newBand;
                }

                if (oldFreq != newFreq) // do all meters, as same data for all
                {
                    foreach (KeyValuePair<string, clsMeter> ms in _meters)
                    {
                        clsMeter m = ms.Value;
                        m.AntennasChanged(newBand, Band.FIRST, newFreq, -1);
                    }
                }
            }
            bandChange(rx, oldBand, newBand, true, false);
        }
        private static void OnVFOB(Band oldBand, Band newBand, DSPMode oldMode, DSPMode newMode, Filter oldFilter, Filter newFilter, double oldFreq, double newFreq, double oldCentreF, double newCentreF, bool oldCTUN, bool newCTUN, int oldZoomSlider, int newZoomSlider, double offset, int rx)
        {
            //_rx2VHForAbove = _console.RX2Enabled && newFreq/*_console.VFOBFreq*/ >= 30;
            if (rx == 2)
                _rx2VHForAbove = _console.RX2Enabled && newFreq >= _console.S9Frequency;

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = mkvp.Value;
                    m.VfoB = newFreq;
                    m.ModeVfoB = newMode;
                    m.BandVfoB = newBand;
                }
            }
            bandChange(rx, oldBand, newBand, true, false);
        }
        public static void OnVFOASub(Band oldBand, Band newBand, DSPMode newMode, Filter newFilter, double oldFreq, double newFreq, double newCentreF, bool newCTUN, int newZoomSlider, double offset, int rx)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters.Where(o => o.Value.RX == 1)) // only applies to rx1
                {
                    clsMeter m = mkvp.Value;
                    m.VfoSub = newFreq;
                    //m.ModeVfoA = newMode;
                    m.BandVfoASub = newBand;
                }
            }
        }
        private static void initAllConsoleData()
        {
            // list used so that not limited to rx1/rx2
            List<int> rxNumbers = new List<int>();

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    // get rx numbers
                    if (!rxNumbers.Contains(mkvp.Value.RX)) rxNumbers.Add(mkvp.Value.RX);
                }
            }

            // gather all the data again
            foreach (int rx in rxNumbers)
            {
                initConsoleData(rx);
            }
        }
        private static void initConsoleData(clsMeter m)
        {
            m.MOX = _console.MOX;
            m.Split = _console.VFOSplit;
            m.TXVFOb = _console.VFOBTX;
            m.RX2Enabled = _console.RX2Enabled;
            m.MultiRxEnabled = _console.chkEnableMultiRX.Checked;

            m.TXBand = _console.TXBand;

            m.VfoA = _console.VFOAFreq;
            m.ModeVfoA = _console.RX1DSPMode;
            m.BandVfoA = _console.RX1Band;
            m.FilterVfoA = _console.RX1Filter;
            m.FilterVfoAName = getFilterName(1);// _console.rx1_filters[(int)_console.RX1DSPMode].GetName(_console.RX1Filter);

            m.VfoB = _console.VFOBFreq;

            m.VfoSub = _console.VFOASubFreq;

            if (!_console.RX2Enabled)
            {
                // same as rx1 unless rx2 enabled
                m.ModeVfoB = _console.RX1DSPMode;
                m.BandVfoB = _console.RX1BandForVFOB();//_console.RX1Band;
                m.FilterVfoB = _console.RX1Filter;
                m.FilterVfoBName = getFilterName(1);//_console.rx1_filters[(int)_console.RX1DSPMode].GetName(_console.RX1Filter);
            }
            else
            {
                m.ModeVfoB = _console.RX2DSPMode;
                m.BandVfoB = _console.RX2Band;
                m.FilterVfoB = _console.RX2Filter;
                m.FilterVfoBName = getFilterName(2);//_console.rx2_filters[(int)_console.RX2DSPMode].GetName(_console.RX2Filter);
            }

            // vfo b is locked anyway, whatever the rx is, and a wont matter for rx2
            m.VFOALock = _console.VFOALock;
            m.VFOBLock = _console.VFOBLock;

            m.VFOSync = _console.VFOSync;

            m.TXEQEnabled = _console.TXEQ;
            m.LevelerEnabled = !_console.IsSetupFormNull && _console.SetupForm.TXLevelerOn;
            m.CFCEnabled = _console.CFCEnabled;
            m.CompandEnabled = _console.CPDR;

            m.QuickSplitEnabled = _console.GetQuickSplitEnabled;

            if (m.RX == 1)
            {
                if (_console.BandGENSelected)
                    m.BandGroup = BandGroups.GEN;
                if (_console.BandHFSelected)
                    m.BandGroup = BandGroups.HF;
                if (_console.BandVHFSelected)
                    m.BandGroup = BandGroups.VHF;
            }
            else if (m.RX == 2)
            {
                m.BandGroup = m.GetBandGroupFromBand(_console.RX2Band);
            }

            m.TuneStepIndex = _console.TuneStepIndex;

            m.AntennasChanged(_console.RX1Band, _console.TXBand, _console.VFOAFreq, _console.TXFreq);

            // update any filter meter items and setupbuttons to update filter text
            m.InitFilterButtons();
        }
        private static void initConsoleData(int rx)
        {
            if (_console == null) return;

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters.Where(o => o.Value.RX == rx))
                {
                    clsMeter m = mkvp.Value;

                    initConsoleData(m);
                }
            }
        }
        private static string getFilterName(int rx)
        {
            try
            {
                if (rx == 1)
                {
                    if (_console.RX1DSPMode == DSPMode.FIRST || _console.RX1DSPMode == DSPMode.LAST ||
                        _console.RX1Filter == Filter.FIRST || _console.RX1Filter == Filter.LAST) return "";

                    return _console.rx1_filters[(int)_console.RX1DSPMode].GetName(_console.RX1Filter);
                }
                else if (rx == 2)
                {
                    if (_console.RX2DSPMode == DSPMode.FIRST || _console.RX2DSPMode == DSPMode.LAST ||
                        _console.RX2Filter == Filter.FIRST || _console.RX2Filter == Filter.LAST) return "";

                    return _console.rx2_filters[(int)_console.RX2DSPMode].GetName(_console.RX2Filter);
                }
                else
                {
                    return "";
                }
            }
            catch
            {
                return "";
            }
        }
        private static string getFilterName(int rx, Filter f)
        {
            try
            {
                if (rx == 1)
                {
                    if (_console.RX1DSPMode == DSPMode.FIRST || _console.RX1DSPMode == DSPMode.LAST ||
                        _console.RX1Filter == Filter.FIRST || _console.RX1Filter == Filter.LAST) return f.ToString();

                    return _console.rx1_filters[(int)_console.RX1DSPMode].GetName(f);
                }
                else if (rx == 2)
                {
                    if (_console.RX2DSPMode == DSPMode.FIRST || _console.RX2DSPMode == DSPMode.LAST ||
                        _console.RX2Filter == Filter.FIRST || _console.RX2Filter == Filter.LAST) return f.ToString();

                    return _console.rx2_filters[(int)_console.RX2DSPMode].GetName(f);
                }
                else
                {
                    return "";
                }
            }
            catch
            {
                return "";
            }
        }
        private static void OnPower(bool oldPower, bool newPower)
        {
            //_power = newPower;

            if(oldPower != newPower)
            {
                lock (_metersLock)
                {
                    foreach (KeyValuePair<string, clsMeter> ms in _meters)
                    {
                        clsMeter m = ms.Value;

                        m.ZeroOut(true, true);
                    }
                }
            }
        }
        private static void OnPreMox(int rx, bool oldMox, bool newMox)
        {
            lock (_metersLock)
            {
                // ignores any readings that comes in after the premox event, is reset in OnMox which happens at end of console mox transition code
                // prevents inflight readings from causing glitches/errors in readings
                if (_readingIgnore != null && _readingIgnore.ContainsKey(rx))
                    _readingIgnore[rx] = true;
            }
        }
        private static void OnMox(int rx, bool oldMox, bool newMox)
        {
            lock (_metersLock)
            {
                List<string> to_hide = new List<string>();
                List<string> to_show = new List<string>();

                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;

                    bool previousMOX = m.MOX;
                    m.MOX = rx == m.RX && newMox;

                    if (newMox && !oldMox) // ignore RX as tx stuff is common
                    {
                        // now tx from rx
                        m.ZeroOut(true, false); // reset rx readings
                    }
                    else if (!newMox && oldMox) // ignore RX as tx stuff is common
                    {
                        // now rx from tx
                        m.ZeroOut(false, true); // reset tx readings
                    }

                    if (_lstUCMeters.ContainsKey(m.ID))
                    {
                        ucMeter uc = _lstUCMeters[m.ID];
                        if (m.MOX && !previousMOX) // consider the RX unlike above
                        {
                            if (!uc.MeterEnabled && uc.ShowOnTX) to_show.Add(m.ID);
                            if (uc.MeterEnabled && !uc.ShowOnTX) to_hide.Add(m.ID);
                        }
                        if (!m.MOX && previousMOX)
                        {
                            if (!uc.MeterEnabled && uc.ShowOnRX) to_show.Add(m.ID);
                            if (uc.MeterEnabled && !uc.ShowOnRX) to_hide.Add(m.ID);
                        }
                    }
                }

                //show first, then hide, so we dont get a flicker, where container vanishes then shows sometime after
                foreach(string id in to_show)
                {
                    enableContainer(id, true);
                }
                foreach (string id in to_hide)
                {
                    enableContainer(id, false);
                }

                if (_readingIgnore != null && _readingIgnore.ContainsKey(rx))
                    _readingIgnore[rx] = false;
            }
        }
        private static void OnEQChanged(bool oldState, bool newState)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;
                    m.TXEQEnabled = newState;

                    m.DisableMeterType(MeterType.EQ, !newState);
                }
            }
        }
        private static void OnLevelerChanged(bool oldState, bool newState)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;
                    m.LevelerEnabled = newState;

                    m.DisableMeterType(MeterType.LEVELER, !newState);
                    m.DisableMeterType(MeterType.LEVELER_GAIN, !newState);
                }
            }
        }
        private static void OnCFCChanged(bool oldState, bool newState)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;
                    m.CFCEnabled = newState;

                    m.DisableMeterType(MeterType.CFC, !newState);
                    m.DisableMeterType(MeterType.CFC_GAIN, !newState);
                }
            }
        }
        private static void OnCompandChanged(bool oldState, bool newState)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;
                    m.CompandEnabled = newState;

                    m.DisableMeterType(MeterType.COMP, !newState);
                }
            }
        }
        private static void OnQuickSplitChanged(bool oldState, bool newState)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> mkvp in _meters)
                {
                    clsMeter m = mkvp.Value;
                    m.QuickSplitEnabled = newState;
                }
            }
        }
        public static int CurrentPowerRating
        {
            get
            {
                // power choice based on code from console.getMeterPixelPosAndDrawScales
                // TODO: 8000mk2  !!!!!!!!!!!!!!!!!!!!
                // TODO: ANAN_G2_1K

                int nWatts = 500;

                if (_alexPresent && 
                    ((_currentHPSDRmodel == HPSDRModel.ORIONMKII || _currentHPSDRmodel == HPSDRModel.ANAN8000D || _currentHPSDRmodel == HPSDRModel.ANAN_G2) 
                    && _transverterIndex < 0))
                {
                    nWatts = 200;
                }
                else if ((_alexPresent || _paPresent) &&
                                (_currentHPSDRmodel != HPSDRModel.ANAN10 &&
                                 _currentHPSDRmodel != HPSDRModel.ANAN10E &&
                                !_apolloPresent))
                {
                    nWatts = 100;
                }
                else if (_currentHPSDRmodel == HPSDRModel.ANAN10 ||
                                _currentHPSDRmodel == HPSDRModel.ANAN10E)
                {
                    nWatts = 30;
                }
                else if (_apolloPresent)
                {
                    nWatts = 15;
                }
                else
                {
                    nWatts = 1;
                }
                
                return nWatts;
            }
        }
        private static float dbmOffsetForAboveS9Frequency(int rx)
        {
            if (IsAboveS9Frequency(rx))
                return 20f;
            else
                return 0f;
        }
        private static bool IsAboveS9Frequency(int rx)
        {
            return (rx == 1 && _rx1VHForAbove) || (rx == 2 && _rx2VHForAbove);
        }
        //private static float normalisePower()
        //{
        //    return a factor to apply to power values to bring them to 100w
        //     this is needed because power meter scaling is based on 100w at full deflection
        //    switch (CurrentPowerRating)
        //    {
        //        case 500: return 1 / 5f;
        //        case 200: return 1 / 2f;
        //        case 100: return 1f;
        //        case 30: return 100 / 30f;
        //        case 15: return 100 / 15f;
        //        case 1: return 100f;
        //    }

        //    return 1f;

        //}
        private static float getReading(int rx, Reading rt, bool bUseReading = false)
        {
            if (rt == Reading.NONE) return -200f;

            //lock (_readingsLock)
            {
                return _readings[rx].GetReading(rt, bUseReading);
            }
        }
        private static void setReading(int rx, Reading rt, ref Dictionary<Reading, float> readings, bool bChangeOverride = false)
        {
            // locked outside
            if (_readings[rx].RequiresUpdate(rt) && (!_readingIgnore[rx] || bChangeOverride)) _readings[rx].SetReading(rt, readings[rt]);
        }
        private static void setReadingForced(int rx, Reading rt, float reading)
        {
            //lock (_readingsLock)
            {
                _readings[rx].SetReading(rt, reading);
            }
        }
        private static void OnMeterReadings(int rx, bool mox, ref Dictionary<Reading, float> readings)
        {
            //lock (_readingsLock)
            {
                //
                if (!mox)
                {
                    setReading(rx, Reading.SIGNAL_STRENGTH, ref readings);
                    setReading(rx, Reading.AVG_SIGNAL_STRENGTH, ref readings);
                    setReading(rx, Reading.ADC_PK, ref readings);
                    setReading(rx, Reading.ADC_AV, ref readings);
                    setReading(rx, Reading.AGC_PK, ref readings);
                    setReading(rx, Reading.AGC_AV, ref readings);
                    setReading(rx, Reading.AGC_GAIN, ref readings);
                    //setReading(rx, Reading.EYE_PERCENT, ref readings);

                    setReading(rx, Reading.ESTIMATED_PBSNR, ref readings);
                }
                else
                {
                    setReading(rx, Reading.MIC, ref readings);
                    setReading(rx, Reading.MIC_PK, ref readings);
                    setReading(rx, Reading.EQ_PK, ref readings);
                    setReading(rx, Reading.EQ, ref readings);
                    setReading(rx, Reading.LEVELER_PK, ref readings);
                    setReading(rx, Reading.LEVELER, ref readings);
                    setReading(rx, Reading.LVL_G, ref readings);
                    setReading(rx, Reading.CFC_PK, ref readings);
                    setReading(rx, Reading.CFC_AV, ref readings);
                    setReading(rx, Reading.CFC_G, ref readings);
                    //setReading(rx, Reading.CPDR_PK, ref readings);
                    //setReading(rx, Reading.CPDR, ref readings);

                    setReading(rx, Reading.ALC_PK, ref readings);
                    setReading(rx, Reading.ALC, ref readings);
                    setReading(rx, Reading.ALC_G, ref readings);

                    setReading(rx, Reading.COMP, ref readings);
                    setReading(rx, Reading.COMP_PK, ref readings);
                    setReading(rx, Reading.ALC_GROUP, ref readings);

                    //setReading(rx, Reading.CPDR, ref readings);//CPDR is the same as comp
                    //setReading(rx, Reading.CPDR_PK, ref readings);//CPDR is the same as comp

                    setReading(rx, Reading.PWR, ref readings);
                    setReading(rx, Reading.REVERSE_PWR, ref readings);
                    setReading(rx, Reading.SWR, ref readings);

                    if (rx == 1)
                    { // only rx1 data
                        setReading(rx, Reading.DRIVE_FWD_ADC, ref readings);
                        setReading(rx, Reading.FWD_ADC, ref readings);
                        setReading(rx, Reading.REV_ADC, ref readings);
                        setReading(rx, Reading.DRIVE_PWR, ref readings);
                        setReading(rx, Reading.PA_FWD_PWR, ref readings);
                        setReading(rx, Reading.PA_REV_PWR, ref readings);
                        setReading(rx, Reading.CAL_FWD_PWR, ref readings);
                    }
                }
                setReading(rx, Reading.VOLTS, ref readings);
                setReading(rx, Reading.AMPS, ref readings);    
            }
        }
        public static bool RequiresUpdate(int rx, Reading rt)
        {
            //lock (_readingsLock)
            {
                return _readings[rx].RequiresUpdate(rt);
            }
        }

        // meters
        public static int QuickestUpdateInterval(int rx, bool mox)
        {
            int updateRate = int.MaxValue;

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> kvp in _meters.Where(kvp => kvp.Value.RX == rx))
                {
                    int ui = kvp.Value.QuickestUpdateInterval(mox);
                    if (ui < updateRate) updateRate = ui;
                }
            }

            if (updateRate == int.MaxValue) updateRate = 250; // none found, use 250mS

            return updateRate;
        }

        public static clsMeter MeterFromId(string sId)
        {
            lock (_metersLock)
            {
                if (_meters == null) return null;
                if (!_meters.ContainsKey(sId)) return null;

                return _meters[sId];
            }
        }
        public static bool MeterExists(string sId)
        {
            lock (_metersLock)
            {
                if (_meters == null) return false;
                return _meters.ContainsKey(sId);
            }
        }
        public static void GlobalKeyDown(Keys keycode)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> kvp in _meters)
                {
                    clsMeter m = kvp.Value;
                    m.KeyDown(keycode);
                }
            }
        }
        public static void GlobalKeyUp(Keys keycode)
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, clsMeter> kvp in _meters)
                {
                    clsMeter m = kvp.Value;
                    m.KeyUp(keycode);
                }
            }
        }
        public static void AddMeterContainer(ucMeter ucM, bool bFromRestore = false)
        {
            if (_console == null) return;

            lock (_metersLock)
            {
                bool bEnabled = ucM.MeterEnabled;

                ucM.Console = _console;
                ucM.FloatingDockedClicked += ucMeter_FloatingDockedClicked;
                ucM.DockedMoved += ucMeter_FloatingDockedMoved;
                ucM.SettingsClicked += ucMeter_SettingsClicked;

                // float form
                frmMeterDisplay f = new frmMeterDisplay(_console, ucM.RX);
                f.ID = ucM.ID;

                // if we are recoving, we need to apply the form size to the user control
                // size, and in turn this define the target size for the dx renderer.
                // If we don't do this there will be a few frames where the target size is incorrect and the height calculated
                // will be from the default container size of 400x200.
                // Note: f.ID above causes the form sizes to be reloaded from the db
                if(ucM.Floating && bFromRestore)
                    ucM.Size = new Size(f.Size.Width, f.Size.Height);

                // meter items
                clsMeter meter = new clsMeter(ucM.RX, ucM.Name, 1f, 1f);
                meter.ID = ucM.ID;
                meter.Enabled = bEnabled;                

                // a renderer
                addRenderer(ucM.ID, ucM.RX, ucM.DisplayContainer, meter, ucM.BackColor);

                lock (_metersLock)
                {
                    // store everything
                    _lstMeterDisplayForms.Add(f.ID, f);
                    _lstUCMeters.Add(ucM.ID, ucM);
                    _meters.Add(meter.ID, meter);
                }

                // init all the meter info from console
                initConsoleData(ucM.RX);

                if (bEnabled && ucM.MeterEnabled && !bFromRestore) //ignore if we are restoring from db. Meters are shown by FinishSetupAndDisplay() from console in this case
                {
                    if (ucM.Floating)
                    {
                        setMeterFloating(ucM, f);
                    }
                    else
                        returnMeterFromFloating(ucM, f);
                }
            }
        }
        public static bool IsOnTop(string sId)
        {
            bool top;
            lock (_metersLock)
            {
                if (_lstUCMeters == null) return false;
                if (!_lstUCMeters.ContainsKey(sId)) return false;

                ucMeter uc = _lstUCMeters[sId];
                top = uc.IsTopMost;
            }
            return top;
        }
        public static void FinishSetupAndDisplay()
        {
            _finishedSetup = true;

            if (_lstUCMeters == null || _lstUCMeters.Count == 0) return;

            initAllConsoleData(); //[2.10.3.6]MW0LGE get all console info here, as everything will be at the correct state
            zeroAllMeters();

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, ucMeter> ucms in _lstUCMeters)
                {
                    ucMeter ucm = ucms.Value;
                    if (_lstMeterDisplayForms.ContainsKey(ucm.ID) && ucm.MeterEnabled)
                    {
                        // setup
                        frmMeterDisplay f = _lstMeterDisplayForms[ucm.ID];

                        if (ucm.Floating)
                        {
                            setMeterFloating(ucm, f);
                        }
                        else
                            returnMeterFromFloating(ucm, f);
                    }
                }
            }
        }
        public static void FinishSetupAndDisplay(string sID)
        {
            if (_lstUCMeters == null || _lstUCMeters.Count == 0) return;

            lock (_metersLock)
            {
                if (!_meters.ContainsKey(sID)) return;

                clsMeter m = _meters[sID];
                initConsoleData(m);
                m.ZeroOut(true, true);

                if (_lstUCMeters.ContainsKey(sID) && _lstMeterDisplayForms.ContainsKey(sID))
                {
                    ucMeter ucm = _lstUCMeters[sID];

                    frmMeterDisplay f = _lstMeterDisplayForms[ucm.ID];
                    if (ucm.Floating)
                    {
                        setMeterFloating(ucm, f);
                    }
                    else
                        returnMeterFromFloating(ucm, f);
                }
            }
        }
        public static void BringToFront()
        {
            if (_lstUCMeters == null || _lstUCMeters.Count == 0) return;

            lock (_metersLock)
            {
                foreach (KeyValuePair<string, ucMeter> ucms in _lstUCMeters)
                {
                    ucMeter ucm = ucms.Value;
                    if (!ucm.Floating)
                        ucm.BringToFront();
                }
            }
        }
        public static string AddMeterContainer(int nRx, bool bFloating)
        {
            ucMeter ucM = new ucMeter();
            ucM.RX = nRx;
            ucM.Floating = bFloating;

            AddMeterContainer(ucM);
            RunRendererDisplay(ucM.ID);

            return ucM.ID;
        }
        public static int TotalMeterContainers
        {
            get 
            {
                lock (_metersLock)
                {
                    return _lstUCMeters.Count;
                }
            }
        }
        private static void ucMeter_SettingsClicked(object sender, EventArgs e)
        {
            ucMeter ucM = (ucMeter)sender;

            if (!_console.IsSetupFormNull)
                _console.SetupForm.ShowMultiMeterSetupTab(ucM.ID);
        }

        private static void ucMeter_FloatingDockedClicked(object sender, EventArgs e)
        {
            ucMeter ucM = (ucMeter)sender;

            if (!_lstMeterDisplayForms.ContainsKey(ucM.ID)) return;

            if (ucM.Floating)
                returnMeterFromFloating(ucM, _lstMeterDisplayForms[ucM.ID]);
            else
                setMeterFloating(ucM, _lstMeterDisplayForms[ucM.ID]);
        }
        private static void ucMeter_FloatingDockedMoved(object sender, EventArgs e)
        {
            ucMeter ucM = (ucMeter)sender;

            ucM.Delta = new System.Drawing.Point(_console.HDelta, _console.VDelta);
        }
        public static void SetPositionOfDockedMeters()
        {
            lock (_metersLock)
            {
                foreach (KeyValuePair<string, ucMeter> kvp in _lstUCMeters)
                {
                    setPoisitionOfDockedMeter(kvp.Value);
                }
            }
        }
        private static void setPoisitionOfDockedMeter(ucMeter m)
        {
            if (_console == null) return;

            int HDelta = _console.HDelta;
            int VDelta = _console.VDelta;

            if (!m.Floating)
            {
                System.Drawing.Point newLocation = new System.Drawing.Point();

                switch (m.AxisLock)
                {
                    case Axis.RIGHT:
                    case Axis.BOTTOMRIGHT:
                        //case Axis.NONE:
                        newLocation = new System.Drawing.Point(m.DockedLocation.X - m.Delta.X + HDelta, m.DockedLocation.Y - m.Delta.Y + VDelta);
                        break;
                    case Axis.BOTTOMLEFT:
                    case Axis.LEFT:
                        newLocation = new System.Drawing.Point(m.DockedLocation.X, m.DockedLocation.Y - m.Delta.Y + VDelta);
                        break;
                    case Axis.TOPLEFT:
                        newLocation = new System.Drawing.Point(m.DockedLocation.X, m.DockedLocation.Y);
                        break;
                    case Axis.TOP:
                        newLocation = new System.Drawing.Point(m.DockedLocation.X - m.Delta.X + HDelta, m.DockedLocation.Y);
                        break;
                    case Axis.BOTTOM:
                        newLocation = new System.Drawing.Point(m.DockedLocation.X - m.Delta.X + HDelta, m.DockedLocation.Y - m.Delta.Y + VDelta);
                        break;
                    case Axis.TOPRIGHT:
                        newLocation = new System.Drawing.Point(m.DockedLocation.X - m.Delta.X + HDelta, m.DockedLocation.Y);
                        break;
                }

                //limit to client area
                if (newLocation.X + m.Width > _console.Width) newLocation.X = _console.Width - m.Width;
                if (newLocation.Y + m.Height > _console.Height) newLocation.Y = _console.Height - m.Height;
                if (newLocation.X < 0) newLocation.X = 0;
                if (newLocation.Y < 0) newLocation.Y = 0;
                
                if(newLocation != m.Location) m.Location = newLocation;
            }
        }
        private static void returnMeterFromFloating(ucMeter m, frmMeterDisplay frm)
        {
            if (_console == null) return;

            frm.Floating = false;
            frm.Hide();
            m.Hide();
            m.Parent = _console;
            m.Anchor = AnchorStyles.None;// AnchorStyles.Right | AnchorStyles.Bottom;
            m.RestoreLocation();
            m.Floating = false;
            setPoisitionOfDockedMeter(m);
            m.BringToFront();

            if (m.RX == 2 && !_console.RX2Enabled) return;

            if (!_finishedSetup) return;
            m.Show();
        }
        private static void setMeterFloating(ucMeter m, frmMeterDisplay frm)
        {
            if (_console == null) return;

            if (m.RX == 2 && !_console.RX2Enabled) return;

            m.Hide();
            m.Repaint();
            frm.TakeOwner(m);
            frm.Floating = true;
            m.Floating = true;

            //if (m.RX == 2 && !_console.RX2Enabled) return;

            if (!_finishedSetup) return;

            //frm.Opacity = 0f;
            frm.Show();
            //Common.FadeIn(frm, 100);
        }
        private static void OnRX2EnabledChanged(bool enabled)
        {
            lock (_metersLock)
            {
                if (enabled)
                {
                    foreach (KeyValuePair<string, ucMeter> kvp in _lstUCMeters.Where(o => o.Value.RX == 2))
                    {
                        ucMeter ucM = kvp.Value;

                        if (_lstMeterDisplayForms.ContainsKey(ucM.ID) && ucM.MeterEnabled)
                        {
                            if (ucM.Floating)
                                setMeterFloating(ucM, _lstMeterDisplayForms[ucM.ID]);
                            else
                                returnMeterFromFloating(ucM, _lstMeterDisplayForms[ucM.ID]);
                        }
                    }
                }
            }

            initAllConsoleData();
        }
        private static void OnRX2EnabledPreChanged(bool enabled)
        {
            if (!enabled)
            {
                lock (_metersLock)
                {
                    foreach (KeyValuePair<string, ucMeter> kvp in _lstUCMeters.Where(o => o.Value.RX == 2))
                    {
                        ucMeter ucM = kvp.Value;

                        if (!_lstMeterDisplayForms.ContainsKey(ucM.ID)) return;

                        if (ucM.Floating)
                            _lstMeterDisplayForms[ucM.ID].Hide();
                        else
                        {
                            ucM.Hide();
                            ucM.Repaint();
                        }
                    }
                }
            }
        }
        private static void zeroAllMeters()
        {
            lock(_metersLock)
            {
                foreach(KeyValuePair<string, clsMeter> kvp in _meters)
                {
                    clsMeter m = kvp.Value;
                    m.ZeroOut(true, true);
                }
            }
        }
        public static bool RestoreSettings(ref Dictionary<string, string> settings)
        {
            bool bRestoreOk = true;
            try
            {
                foreach (KeyValuePair<string, string> kvp in settings.Where(o => o.Key.StartsWith("meterContData_")))
                {
                    ucMeter ucM = new ucMeter();
                    bool bUcMeterOk = ucM.TryParse(kvp.Value);

                    if (bUcMeterOk)
                    {
                        if (!MeterExists(ucM.ID))
                        {
                            AddMeterContainer(ucM, true);

                            clsMeter m = MeterFromId(ucM.ID);

                            if (m != null)
                            {
                                // now the meter
                                IEnumerable<KeyValuePair<string, string>> meterData = settings.Where(o => o.Key.StartsWith("meterData_" + m.ID));
                                if (meterData != null && meterData.Count() == 1)
                                {
                                    KeyValuePair<string, string> md = meterData.First();

                                    clsMeter tmpMeter = new clsMeter(1, ucM.Name); // dummy init data, will get replaced by tryparse below
                                    tmpMeter.TryParse(md.Value);

                                    // copy to actual meter
                                    // id will be the same
                                    m.Name = tmpMeter.Name;
                                    m.RX = tmpMeter.RX;
                                    m.XRatio = tmpMeter.XRatio;
                                    m.YRatio = tmpMeter.YRatio;
                                    m.DisplayGroup = tmpMeter.DisplayGroup;
                                    m.PadX = tmpMeter.PadX;
                                    m.PadY = tmpMeter.PadY;
                                    m.Height = tmpMeter.Height;
                                }

                                // finally the groups
                                IEnumerable<KeyValuePair<string, string>> meterIGData = settings.Where(o => o.Key.StartsWith("meterIGData_") && o.Value.Contains(m.ID)); // parent id, stored in value
                                foreach (KeyValuePair<string, string> igd in meterIGData)
                                {
                                    clsItemGroup ig = new clsItemGroup();
                                    bool bOk = ig.TryParse(igd.Value);

                                    if (bOk)
                                    {
                                        m.AddMeter(ig.MeterType, ig);

                                        //and the settings
                                        //let us check if version 2 and use that
                                        IEnumerable<KeyValuePair<string, string>> meterIGSettings;
                                        meterIGSettings = settings.Where(o => o.Key.StartsWith("meterIGSettings_2_" + ig.ID));
                                        if (meterIGSettings != null && meterIGSettings.Count() == 1)
                                        {
                                            clsIGSettings igs = new clsIGSettings();
                                            bool bIGSok = igs.TryParse2(meterIGSettings.First().Value);
                                            if (bIGSok) m.ApplySettingsForMeterGroup(ig.MeterType, igs, ig.Order);
                                        }
                                        else
                                        {
                                            meterIGSettings = settings.Where(o => o.Key.StartsWith("meterIGSettings_" + ig.ID) && !o.Key.StartsWith("meterIGSettings_2_"));
                                            if (meterIGSettings != null && meterIGSettings.Count() == 1)
                                            {
                                                clsIGSettings igs = new clsIGSettings();
                                                bool bIGSok = igs.TryParse(meterIGSettings.First().Value);
                                                if (bIGSok) m.ApplySettingsForMeterGroup(ig.MeterType, igs, ig.Order);
                                            }
                                        }
                                    }
                                }
                                m.ZeroOut(true, true);

                                m.Rebuild();
                            }
                        }
                    }
                }
            }
            catch
            {
                bRestoreOk = false;
            }
            return bRestoreOk;
        }        
        public static List<string> GetFormGuidList()
        {
            List<string> sGuidList = new List<string>();

            lock (_metersLock)
            {
                foreach(KeyValuePair<string, frmMeterDisplay> kvp in _lstMeterDisplayForms)
                {
                    frmMeterDisplay fmd = kvp.Value;

                    sGuidList.Add(fmd.ID);
                }
            }

            return sGuidList;
        }
        public static bool StoreSettings2(ref Dictionary<string, string> a)
        {
            if (a == null) return false;

            bool bStoreOk = true;

            try
            {
                lock (_metersLock)
                {
                    // meter container data
                    foreach (KeyValuePair<string, ucMeter> kvp in _lstUCMeters)
                    {
                        a.Add("meterContData_" + kvp.Value.ID, kvp.Value.ToString());

                        // then the meter itself which holds multiple meter items
                        clsMeter m = MeterFromId(kvp.Value.ID);
                        if (m != null)
                        {
                            a.Add("meterData_" + kvp.Value.ID, m.ToString());

                            Dictionary<string, clsItemGroup> groupItems = m.getMeterGroups();

                            // then each meter item which are groups in this case
                            foreach (KeyValuePair<string, clsItemGroup> ig in groupItems)
                            {
                                Dictionary<string, clsMeterItem> mis = m.itemsFromID(ig.Value.ID);
                                if (mis != null)
                                {
                                    foreach (KeyValuePair<string, clsMeterItem> kvp2 in mis.Where(o => o.Value.StoreSettings == true))
                                    {
                                        clsMeterItem mi = kvp2.Value;
                                        if (mi != null && mi.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP)
                                        {
                                            a.Add("meterIGData_" + kvp2.Value.ID, mi.ToString());
                                        }
                                    }
                                }

                                clsIGSettings igs = m.GetSettingsForMeterGroup(ig.Value.MeterType, ig.Value.Order);
                                if (igs != null)
                                {
                                    //a.Add("meterIGSettings_" + ig.Value.ID, igs.ToString()); //[2.10.3.6]MW0LGE not used
                                    a.Add("meterIGSettings_2_" + ig.Value.ID, igs.ToString2());
                                }
                            }
                        }
                    }
                }
            }
            catch
            {
                bStoreOk = false;
            }

            return bStoreOk;
        }
        //public static bool StoreSettings(ref ArrayList a)
        //{
        //    if (a == null) return false;

        //    bool bStoreOk = true;

        //    try
        //    {
        //        lock (_metersLock)
        //        {
        //            // meter container data
        //            foreach (KeyValuePair<string, ucMeter> kvp in _lstUCMeters)
        //            {
        //                a.Add("meterContData_" + kvp.Value.ID + "/" + kvp.Value.ToString());

        //                // then the meter itself which holds multiple meter items
        //                clsMeter m = MeterFromId(kvp.Value.ID);
        //                if (m != null)
        //                {
        //                    a.Add("meterData_" + kvp.Value.ID + "/" + m.ToString());

        //                    Dictionary<string, clsItemGroup> groupItems = m.getMeterGroups();

        //                    // then each meter item which are groups in this case
        //                    foreach (KeyValuePair<string, clsItemGroup> ig in groupItems)
        //                    {
        //                        Dictionary<string, clsMeterItem> mis = m.itemsFromID(ig.Value.ID);
        //                        if (mis != null)
        //                        {
        //                            foreach (KeyValuePair<string, clsMeterItem> kvp2 in mis.Where(o => o.Value.StoreSettings == true))
        //                            {
        //                                clsMeterItem mi = kvp2.Value;
        //                                if (mi != null)
        //                                {
        //                                    a.Add("meterIGData_" + kvp2.Value.ID + "/" + mi.ToString());
        //                                }
        //                            }
        //                        }

        //                        clsIGSettings igs = m.GetSettingsForMeterGroup(ig.Value.MeterType);
        //                        if (igs != null)
        //                        {
        //                            a.Add("meterIGSettings_" + ig.Value.ID + "/" + igs.ToString());
        //                        }
        //                    }
        //                }
        //            }
        //        }
        //    }
        //    catch
        //    {
        //        bStoreOk = false;
        //    }

        //    return bStoreOk;
        //}
        public static Dictionary<string, ucMeter> MeterContainers
        {
            get 
            {
                lock (_metersLock)
                {
                    return _lstUCMeters;
                }
            }
        }
        public static void RemoveMeterContainer(string sId)
        {
            lock (_metersLock)
            {
                if (!_lstUCMeters.ContainsKey(sId)) return;
                if (!_lstMeterDisplayForms.ContainsKey(sId)) return;

                frmMeterDisplay f = _lstMeterDisplayForms[sId];
                f.Hide();

                ucMeter uc = _lstUCMeters[sId];
                uc.Hide();
                uc.Repaint();
                // unreg delegates
                uc.FloatingDockedClicked -= ucMeter_FloatingDockedClicked;
                uc.DockedMoved -= ucMeter_FloatingDockedMoved;
                uc.SettingsClicked -= ucMeter_SettingsClicked;                

                removeRenderer(sId);

                f.Close();

                _lstMeterDisplayForms.Remove(sId);

                _console.Controls.Remove(uc);
                _lstUCMeters.Remove(sId);

                if (_meters.ContainsKey(sId))
                {
                    clsMeter m = _meters[sId];
                    m.RemoveAllMeterTypes();
                    _meters.Remove(sId);
                }
            }
        }
        #endregion       
        #region clsMeterItem
        public class clsMeterItem
        {
            public enum MeterItemType
            {
                BASE = 0,
                H_BAR,
                V_BAR,
                H_SCALE,
                V_SCALE,
                NEEDLE,
                NEEDLE_SCALE_PWR,
                TEXT,
                IMAGE,
                SOLID_COLOUR,
                CLICKBOX,
                MAGIC_EYE,
                HISTORY,
                ITEM_GROUP,
                VFO_DISPLAY,
                CLOCK,
                SIGNAL_TEXT_DISPLAY,
                FADE_COVER,
                SPACER,
                TEXT_OVERLAY,
                DATA_OUT,
                ROTATOR,
                LED,
                WEB_IMAGE,
                BAND_BUTTONS,
                MODE_BUTTONS,
                FILTER_BUTTONS,
                ANTENNA_BUTTONS,
                TUNESTEP_BUTTONS,
                DISCORD_BUTTONS
                //SPECTRUM
            }

            public class clsPercCache
            {
                public float _value;
                public float _percX;
                public float _percY;
                public PointF _min;
                public PointF _max;
            }
            private Dictionary<float, clsPercCache> _percCache;
            private Queue<float> _percCacheKey;

            private string _sId;
            private string _sParentId;

            private MeterItemType _ItemType;

            private bool _storeSettings;

            private PointF _topLeft; // 0-1,0-1
            private PointF _displayTopLeft; // 0-1,0-1
            private SizeF _size;  // 0-1,0-1
            private int _zOrder; // lower first
            private int _msUpdateInterval; //ms
            private float _attackRatio; // 0f - 1f
            private float _decayRatio; // 0f - 1f
            private float _value;
            private System.Drawing.Color _historyColour;
            private Reading _readingType;
            private Stopwatch _updateStopwatch;
            private Dictionary<float, PointF> _scaleCalibration;
            private int _displayGroup;
            private bool _bNormaliseTo100W;
            private bool _bOnlyWhenTx;
            private bool _bOnlyWhenRx;
            private bool _bFadeOnRx;
            private bool _bFadeOnTx;
            private bool _bPrimary;
            private float _maxPower;
            private string _readingName;

            private int _fadeValue;
            private bool _disabled;
            private bool _mox_fade_toggle;

            //private Guid[] _mmio_guid;
            //private string[] _mmio_variable;
            private Guid _mmio_guid;
            private string _mmio_variable;
            private int _mmio_variable_index;

            private PointF _mouseDownPoint;
            private PointF _mouseUpPoint;
            private PointF _mouseMovePoint;
            private bool _mouse_entered;
            private bool _mouseButtonDown;
            private MouseButtons _mouseButton;

            private bool _visible;

            public clsMeterItem(clsMeter owningMeter = null)
            {
                // constructor
                _sId = System.Guid.NewGuid().ToString();
                _sParentId = "";
                _ItemType = MeterItemType.BASE;
                _storeSettings = false;
                _readingType = Reading.NONE;
                _readingName = "";
                _topLeft.X = 0;
                _topLeft.Y = 0;
                _displayTopLeft.X = _topLeft.X;
                _displayTopLeft.Y = _topLeft.Y;
                _zOrder = 0;
                _msUpdateInterval = 5000; //ms
                _attackRatio = 0.8f;
                _decayRatio = 0.2f;
                _value = -200f;
                _historyColour = System.Drawing.Color.FromArgb(128, 255, 255, 255);
                _scaleCalibration = new Dictionary<float, PointF>();
                _size.Width = 1f;
                _size.Height = 1f;
                _displayGroup = 0;
                _bNormaliseTo100W = false;
                _bOnlyWhenTx = false;
                _bOnlyWhenRx = false;
                _bFadeOnRx = false;
                _bFadeOnTx = false;
                _bPrimary = false;
                _maxPower = CurrentPowerRating;//100f;
                _percCache = new Dictionary<float, clsPercCache>();
                _percCacheKey = new Queue<float>();
                _updateStopwatch = new Stopwatch();
                _fadeValue = 255;
                _disabled = false;
                _mox_fade_toggle = false;
                _visible = true;

                //_mmio_guid = new Guid[10];
                //_mmio_variable = new string[10];

                //for (int i = 0; i < _mmio_guid.Length; i++)
                //{
                //    _mmio_guid[i] = Guid.Empty;
                //    _mmio_variable[i] = "--DEFAULT--";
                //}
                _mmio_guid = Guid.Empty;
                _mmio_variable = "--DEFAULT--";
                _mmio_variable_index = -1;

                _mouseDownPoint = new PointF(float.MinValue, float.MinValue);
                _mouseUpPoint = new PointF(float.MinValue, float.MinValue);
                _mouseMovePoint = new PointF(float.MinValue, float.MinValue);
                _mouse_entered = false;
                _mouseButtonDown = false;
                _mouseButton = MouseButtons.None;
            }
            //public Guid GetMMIOGuid(int index)
            //{
            //    return _mmio_guid[index];
            //}
            //public void SetMMIOGuid(int index, Guid g)
            //{
            //    _mmio_guid[index] = g;
            //}
            //public string GetMMIOVariable(int index)
            //{
            //    return _mmio_variable[index];
            //}
            //public void SetMMIOVariable(int index, string variable)
            //{
            //    _mmio_variable[index] = variable;
            //}
            public bool Visible
            {
                get { return _visible; }
                set { _visible = value; }
            }
            public Guid MMIOGuid
            {
                get { return _mmio_guid; }
                set { _mmio_guid = value; }
            }
            public string MMIOVariable
            {
                get { return _mmio_variable; }
                set { _mmio_variable = value; }
            }
            public int MMIOVariableIndex
            {
                get { return _mmio_variable_index; }
                set { _mmio_variable_index = value; }
            }
            public bool MOXFadeToggle
            {
                // this is used by the fading system, use meter container for MOX state
                get { return _mox_fade_toggle; }
                set { _mox_fade_toggle = value; }
            }
            public int FadeValue //[2.10.1.0] MW0LGE used for on rx/tx fading
            {
                get { return _fadeValue; }
                set { _fadeValue = value; }
            }
            public bool Disabled //[2.10.1.0] MW0LGE used when certain features turned off such as eq,leveler,cfc
            {
                get { return _disabled; }
                set { _disabled = value; }
            }
            public void AddPerc(clsPercCache pc)
            {
                if (_percCache.ContainsKey(pc._value)) return;

                _percCache.Add(pc._value, pc);
                _percCacheKey.Enqueue(pc._value);
                if (_percCache.Count > 500)
                {
                    float sOldestKey = _percCacheKey.Dequeue();
                    _percCache.Remove(sOldestKey);
                }
            }
            public clsPercCache GetPerc(float value)
            {
                if (!_percCache.ContainsKey(value)) return null;
                return _percCache[value];
            }
            public bool HasPerc(float value)
            {
                return _percCache.ContainsKey(value);
            }
            public string ID { 
                get { return _sId; } 
                set { _sId = value; }
            }
            public string ParentID
            {
                get { return _sParentId; }
                set { _sParentId = value; }
            }
            public bool Primary
            {
                get { return _bPrimary; }
                set { _bPrimary = value; }
            }
            public bool StoreSettings
            {
                get { return _storeSettings; }
                set { _storeSettings = value; }
            }
            public bool FadeOnRx
            {
                get { return _bFadeOnRx; }
                set { _bFadeOnRx = value; }
            }
            public bool FadeOnTx
            {
                get { return _bFadeOnTx; }
                set { _bFadeOnTx = value; }
            }
            public PointF TopLeft {
                get { return _topLeft; }
                set { 
                    _topLeft = value;
                    _displayTopLeft = new PointF(_topLeft.X, _topLeft.Y);
                }
            }
            public PointF DisplayTopLeft
            {
                get { return _displayTopLeft; }
                set { _displayTopLeft = value; }
            }
            public SizeF Size
            {
                get { return _size; }
                set 
                { _size = value; }
            }
            public int DisplayGroup
            {
                get { return _displayGroup; }
                set { _displayGroup = value; }
            }
            public bool NormaliseTo100W
            {
                get { return _bNormaliseTo100W; }
                set { _bNormaliseTo100W = value; }
            }
            public int UpdateInterval
            {
                get { return _msUpdateInterval; }
                set
                {
                    _msUpdateInterval = value;
                    if (_msUpdateInterval < 1) _msUpdateInterval = 1;
                }
            }
            public float AttackRatio
            {
                get { return _attackRatio; }
                set {
                    _attackRatio = value;
                    if (_attackRatio < 0) _attackRatio = 0;
                    if (_attackRatio > 1f) _attackRatio = 1f;
                }
            }
            public float DecayRatio
            {
                get { return _decayRatio; }
                set
                {
                    _decayRatio = value;
                    if (_decayRatio < 0) _decayRatio = 0;
                    if (_decayRatio > 1f) _decayRatio = 1f;
                }
            }
            public MeterItemType ItemType
            {
                get { return _ItemType; }
                set { _ItemType = value; }
            }
            public Reading ReadingSource
            {
                get { return _readingType; }
                set 
                { 
                    _readingType = value;
                    _readingName = MeterManager.ReadingName(_readingType);
                }
            }
            public string ReadingName
            {
                get { return _readingName; }
            }

            public bool RequiresUpdate
            {
                get
                {
                    //if (!_updateStopwatch.IsRunning) _updateStopwatch.Restart();
                    //bool bNeedsUpdate = _updateStopwatch.ElapsedMilliseconds >= _msUpdateInterval;
                    //if(bNeedsUpdate) _updateStopwatch.Stop();

                    bool bNeedsUpdate = DelayUntilNextUpdate <= 0;

                    return bNeedsUpdate;
                }
            }
            public int DelayUntilNextUpdate
            {
                // returns the delay that is needed before we need an update in ms
                get
                {
                    if (!_updateStopwatch.IsRunning) _updateStopwatch.Restart();
                    long nDelay = _msUpdateInterval - _updateStopwatch.ElapsedMilliseconds;
                    if (nDelay <= 0)
                    {
                        _updateStopwatch.Stop();
                        nDelay = 0;
                    }

                    return (int)nDelay;
                }
            }
            public virtual void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // can be overriden by derived

                //// get latest reading
                //float reading = MeterManager.GetReading(rx, ReadingSource);

                //Value = reading;

                //// this reading has been used
                //if (!readingsUsed.Contains(ReadingSource))
                //    readingsUsed.Add(ReadingSource);
            }
            public virtual float Value
            {
                get { return _value; }
                set { _value = value; }
            }
            public int ZOrder
            {
                get { return _zOrder; }
                set { _zOrder = value; }
            }
            public virtual void ClearHistory()
            {
            }
            public virtual void History(out float minHistory, out float maxHistory)
            {
                minHistory = 0f;
                maxHistory = 0f;
            }
            public virtual float MinHistory
            {
                get
                {
                    return 0f;
                }
            }
            public virtual float MaxHistory
            {
                get
                {
                    return 0f;
                }
            }
            public virtual bool ShowHistory
            {
                get { return false; }
                set { }
            }
            public virtual System.Drawing.Color HistoryColour
            {
                get { return _historyColour; }
                set { _historyColour = value; }
            }
            public virtual Dictionary<float, PointF> ScaleCalibration
            {
                get { return _scaleCalibration; }
                set { }
            }            
            public bool OnlyWhenTX
            {
                get { return _bOnlyWhenTx; }
                set { _bOnlyWhenTx = value; }
            }
            public bool OnlyWhenRX
            {
                get { return _bOnlyWhenRx; }
                set { _bOnlyWhenRx = value; }
            }
            public override string ToString()
            {
                return base.ToString();
            }
            public virtual bool TryParse(string val)
            {
                return false;
            }            
            public float MaxPower
            {
                get { return _maxPower; }
                set {                    
                    _maxPower = value;
                    if (_maxPower < 0.1f) _maxPower = CurrentPowerRating; // incase of 0 recovery from db
                }
            }
            public virtual void HandleIncrement()
            {
            }
            public virtual void HandleDecrement()
            {
            }
            public virtual bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                return false;
            }

            // mouse
            public virtual void MouseClick(MouseEventArgs e)
            {

            }
            public virtual void MouseDown(MouseEventArgs e)
            {
                
            }
            public virtual void MouseUp(MouseEventArgs e)
            {

            }
            public virtual MouseButtons MouseButton
            {
                get { return _mouseButton; }
                set { _mouseButton = value; }
            }
            public virtual bool MouseButtonDown
            {
                get { return _mouseButtonDown; }
                set { _mouseButtonDown = value; }
            }
            public virtual PointF MouseDownPoint
            {
                get { return _mouseDownPoint; }
                set { _mouseDownPoint = value; }
            }
            public virtual PointF MouseUpPoint
            {
                get { return _mouseUpPoint; }
                set { _mouseUpPoint = value; }
            }
            public virtual PointF MouseMovePoint
            {
                get { return _mouseMovePoint; }
                set { _mouseMovePoint = value; }
            }
            public virtual bool MouseEntered
            {
                get { return _mouse_entered; }
                set { _mouse_entered = value; }
            }
            public virtual bool ClickHighlight
            {
                get { return false; } set { }
            }
            public virtual void KeyDown(Keys keycode)
            {

            }
            public virtual void KeyUp(Keys keycode)
            {

            }
            public virtual void MouseWheel(int number_of_moves)
            {

            }
            public virtual void Removing()
            {

            }
            public virtual void BandPanelsChanged(bool gen, bool hf, bool vhf)
            {

            }
            public virtual void BandChanged(Band oldBand, Band newBand)
            {

            }
            public virtual void ModeChanged(DSPMode oldMode, DSPMode newMode)
            {

            }
            public virtual void TuneStepIndexChanged(int old_index, int new_index)
            {

            }
        }
        //
        internal class clsItemGroup : clsMeterItem
        {
            private MeterType _meterType;
            private int _order; // 0 = topmost

            public clsItemGroup()
            {
                ItemType = MeterItemType.ITEM_GROUP;
                _meterType = MeterType.NONE;
                StoreSettings = true;
                ZOrder = 999;
            }
            public MeterType MeterType
            {
                get { return _meterType; }
                set { _meterType = value; }
            }
            public int Order
            {
                get { return _order; }
                set { _order = value; }
            }
            public override string ToString()
            {
                string sRet;

                sRet = ID + "|" +
                    ParentID + "|" +
                    ItemType.ToString() + "|" +
                    TopLeft.X.ToString("f4") + "|" +
                    TopLeft.Y.ToString("f4") + "|" +
                    Size.Width.ToString("f4") + "|" +
                    Size.Height.ToString("f4") + "|" +
                    MeterType.ToString() + "|" +
                    Order.ToString();
                    //ZOrder.ToString() + "|" +
                    //UpdateInterval.ToString() + "|" +
                    //AttackRatio.ToString("f4") + "|" +
                    //DecayRatio.ToString("f4") + "|" +
                    //HistoryColour.ToString() + "|" +
                    //ReadingSource.ToString();

                return sRet;
            }
            public override bool TryParse(string str)
            {
                bool bOk = false;
                float x = 0, y = 0, w = 0, h = 0;
                int order = 0;
                MeterType mt = MeterType.LAST;

                if (str != "")
                {
                    string[] tmp = str.Split('|');
                    if (tmp.Length == 9)
                    {
                        bOk = tmp[0] != "";
                        if (bOk) ID = tmp[0];
                        if (bOk) ParentID = tmp[1];
                        //if (bOk) bOk = Enum.TryParse<MeterItemType>(tmp[2], out it);  //ignore

                        if (bOk) bOk = float.TryParse(tmp[3], out x);
                        if (bOk) bOk = float.TryParse(tmp[4], out y);
                        if (bOk) bOk = float.TryParse(tmp[5], out w);
                        if (bOk) bOk = float.TryParse(tmp[6], out h);
                        if (bOk) bOk = Enum.TryParse<MeterType>(tmp[7], out mt);
                        if (bOk)
                            MeterType = mt;
                        if (bOk) bOk = int.TryParse(tmp[8], out order);
                        if (bOk) Order = order;
                    }
                }

                return bOk;
            }
        }
        internal class clsClickBox : clsMeterItem
        {
            private MouseButtons _button;
            private int _gotoGroup;
            private bool _changesGroup;
            private bool _performsIncDec;
            private clsMeterItem _relatedMeterItem = null;

            public clsClickBox()
            {
                ItemType = MeterItemType.CLICKBOX;

                _button = MouseButtons.Left | MouseButtons.Right | MouseButtons.Middle;
                _gotoGroup = 0;
                _changesGroup = true;
                _performsIncDec = false;
                StoreSettings = false;
            }
            public MouseButtons Button
            {
                get { return _button; }
                set { _button = value; }
            }
            public int GotoGroup 
            {
                get { return _gotoGroup; }
                set { _gotoGroup = value; }
            }
            public bool ChangesGroup
            {
                get { return _changesGroup; }
                set { _changesGroup = value; }
            }
            public bool PerformsIncDec
            {
                get { return _performsIncDec; }
                set { _performsIncDec = value; }
            }
            public clsMeterItem RelatedMeterItem
            {
                get { return _relatedMeterItem; }
                set { _relatedMeterItem = value; }
            }
            //public override string ToString()
            //{
            //    string sRet;

            //    sRet = ID + "|" +
            //        ParentID + "|" +
            //        ItemType.ToString() + "|" +
            //        TopLeft.X.ToString("f4") + "|" +
            //        TopLeft.Y.ToString("f4") + "|" +
            //        Size.Width.ToString("f4") + "|" +
            //        Size.Height.ToString("f4") + "|" +
            //        Button.ToString() + "|" +
            //        GotoGroup.ToString("f4") + "|";

            //    return sRet;
            //}
        }
        internal class clsSolidColour : clsMeterItem
        {
            private System.Drawing.Color _colour;
            public clsSolidColour()
            {
                ItemType = MeterItemType.SOLID_COLOUR;
                _colour = System.Drawing.Color.Black;
                StoreSettings = false;
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set
                { 
                    _colour = value;
                }
            }
            //public override string ToString()
            //{
            //    string sRet;

            //    sRet = ID + "|" +
            //        ParentID + "|" +
            //        ItemType.ToString() + "|" +
            //        TopLeft.X.ToString("f4") + "|" +
            //        TopLeft.Y.ToString("f4") + "|" +
            //        Size.Width.ToString("f4") + "|" +
            //        Size.Height.ToString("f4") + "|" +
            //        //
            //        _colour.ToString();

            //    return sRet;
            //}
        }
        internal class clsFadeCover : clsMeterItem
        {
            public clsFadeCover()
            {
                ItemType = MeterItemType.FADE_COVER;
                StoreSettings = false;
                ZOrder = int.MaxValue;
            }
        }
        internal class clsFilterButtonBox : clsButtonBox
        {
            private Filter _filter;
            clsMeter _owningmeter;
            private bool _click_highlight;
            private System.Timers.Timer _click_timer;
            public clsFilterButtonBox(clsMeter owningmeter)
            {
                _owningmeter = owningmeter;
                _click_highlight = false;

                if (_owningmeter.RX == 1)
                {
                    _filter = _owningmeter.FilterVfoA;
                    Buttons = 12;
                }
                else if (_owningmeter.RX == 2)
                {
                    _filter = _owningmeter.FilterVfoB;
                    Buttons = 9;
                }

                ItemType = MeterItemType.FILTER_BUTTONS;                

                setupButtons();
            }
            public void FilterChanged(Filter f, string name)
            {
                Filter old = _filter;
                _filter = f;

                // just set the mode
                int index = (int)_filter - (int)Filter.F1;
                int old_index = (int)old - (int)Filter.F1;

                if(_owningmeter.RX == 2)
                {
                    // only 9 filters, so adjust indexes
                    index -= index > (int)Filter.F7 ? (int)Filter.VAR1 - (int)Filter.F7 - 1 : 0;
                    old_index -= old_index > (int)Filter.F7 ? (int)Filter.VAR1 - (int)Filter.F7 - 1 : 0;
                }

                SetText(1, index, name);
                SetOn(1, old_index, false);
                SetOn(1, index, true);
            }
            public void InitFilterButtons()
            {
                setupButtons();
            }
            //public void FilterNameChanged(Filter f, string new_name)
            //{
            //    // just set the mode
            //    int index = (int)f - (int)Filter.F1;

            //    if (_owningmeter.RX == 2)
            //    {
            //        // only 9 filters, so adjust indexes
            //        index -= index > (int)Filter.F7 ? (int)Filter.VAR1 - (int)Filter.F7 - 1 : 0;
            //    }

            //    SetText(1, index, new_name);
            //}
            private void setupButtons()
            {
                if (!RebuildButtons) return;

                // copy from 0 to 1. 0 is settings bank, 1 is where renderer reads from
                for (int i = 0; i < Buttons; i++)
                {
                    SetText(1, i, GetText(0, i));
                    SetFontColour(1, i, GetFontColour(0, i));
                    SetFontFamily(1, i, GetFontFamily(0, i));
                    SetFontSize(1, i, GetFontSize(0, i));
                    SetFontStyle(1, i, GetFontStyle(0, i));
                    SetUseIndicator(1, i, GetUseIndicator(0, i));
                    SetOnColour(1, i, GetOnColour(0, i));
                    SetOffColour(1, i, GetOffColour(0, i));
                    SetIndicatorWidth(1, i, GetIndicatorWidth(0, i));
                    SetEnabled(1, i, true);
                    SetVisible(1, i, true);
                    SetOn(1, i, false);

                    SetFillColour(1, i, GetFillColour(0, i));
                    SetHoverColour(1, i, GetHoverColour(0, i));
                    SetBorderColour(1, i, GetBorderColour(0, i));

                    SetClickColour(1, i, GetClickColour(0, i));

                    SetUseOffColour(1, i, GetUseOffColour(0, i));

                    SetIndicatorType(1, i, GetIndicatorType(0, i));
                }
                //

                bool update = true;
                if(_console != null)
                {
                    if(_owningmeter.RX == 1)
                    {
                        if (_console.RX1DSPMode == DSPMode.FM || _console.RX1DSPMode == DSPMode.DRM || _console.RX1DSPMode == DSPMode.SPEC) update = false;
                    }
                    else if(_owningmeter.RX == 2)
                    {
                        if (_console.RX2DSPMode == DSPMode.FM || _console.RX2DSPMode == DSPMode.DRM || _console.RX2DSPMode == DSPMode.SPEC) update = false;
                    }
                }

                if (update)
                {
                    int start_filter = (int)Filter.F1;
                    int end_filter = (int)Filter.VAR2;
                    System.Drawing.Color text_color = GetFontColour(0, 0);// System.Drawing.Color.White;

                    int filters = end_filter - start_filter + 1;
                    int offset = 0;
                    for (int i = 0; i < Buttons; i++)
                    {
                        if (i < filters)
                        {
                            Filter f = (Filter)(start_filter + i + offset);
                            string filter_text = getFilterName(_owningmeter.RX, f);

                            SetText(1, i, filter_text);

                            if (GetEnabled(1, i))
                                SetFontColour(1, i, text_color);
                            else
                                SetFontColour(1, i, System.Drawing.Color.FromArgb(255, (int)(text_color.R * 0.3f), (int)(text_color.G * 0.3f), (int)(text_color.B * 0.3f)));

                            SetFontSize(1, i, 18f);

                            SetOn(1, i, f == _filter);

                            if (_owningmeter.RX == 2 && f == Filter.F7)
                                offset = (int)Filter.VAR1 - (int)Filter.F7 - 1; // jump to var1, -1 as i gets incremented, as only 9 filters for rx2
                        }
                    }
                }

                int rows = Buttons / Columns;
                int overflow = Buttons % Columns;
                if (overflow > 0) rows++;

                float half_border = Border / 2f;
                float button_width = ((1f - (0.02f * 2f)) / (float)Columns) - Margin - Border;
                float button_height = ((1f - (0.02f * 2f)) / (float)Columns) * HeightRatio;

                float height = button_height * (float)rows;

                Size = new SizeF(Size.Width, height);
            }
            public override bool ClickHighlight
            {
                get { return _click_highlight; }
            }
            private void setupClick(bool setup)
            {
                if (setup)
                {
                    _click_timer = new System.Timers.Timer(100);
                    _click_timer.Elapsed += (sender, ee) =>
                    {
                        _click_highlight = false;
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    };
                    _click_timer.AutoReset = false;
                    _click_timer.Start();
                }
                else
                {
                    if (_click_timer != null)
                    {
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    }
                    _click_highlight = true;
                }
            }
            public override void MouseDown(MouseEventArgs e)
            {
                Debug.Print("DOWN");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;

                setupClick(false);
            }
            public override void MouseUp(MouseEventArgs e)
            {
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                if (_console == null) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                setupClick(true);

                if (e.Button == MouseButtons.Right)
                {
                    if (_console != null)
                    {
                        _console.BeginInvoke(new MethodInvoker(() =>
                        {
                            _console.PopupFilterContextMenu(_owningmeter.RX ,e);
                        }));
                    }                        
                    return;
                }

                if (_owningmeter.RX == 2 && index > (int)Filter.F7)
                    index += (int)Filter.VAR1 - (int)Filter.F7 - 1;

                Filter f = (Filter)((int)Filter.F1 + index);

                setFilter(f);
            }
            private void setFilter(Filter f)
            {
                //note rx2 only has 9 filters
                if (f == Filter.FIRST) return;

                if (_owningmeter.RX == 2)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.RX2Filter = f;
                    }));
                }
                else
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.RX1Filter = f;
                    }));
                }
            }

            public override int Columns
            {
                get { return base.Columns; }
                set
                {
                    base.Columns = value;
                    setupButtons();
                }
            }
            public override float Border
            {
                get { return base.Border; }
                set
                {
                    base.Border = value;
                    setupButtons();
                }
            }
            public override float Margin
            {
                get { return base.Margin; }
                set
                {
                    base.Margin = value;
                    setupButtons();
                }
            }
            public override float Radius
            {
                get { return base.Radius; }
                set
                {
                    base.Radius = value;
                    setupButtons();
                }
            }
            public override float HeightRatio
            {
                get { return base.HeightRatio; }
                set
                {
                    base.HeightRatio = value;
                    setupButtons();
                }
            }
        }
        internal class clsTunestepButtons : clsButtonBox
        {
            private clsMeter _owningmeter;
            private int _button_bits;
            private clsItemGroup _ig;
            private bool _click_highlight;
            private System.Timers.Timer _click_timer;
            public clsTunestepButtons(clsMeter owningmeter, clsItemGroup ig)
            {
                _owningmeter = owningmeter;
                _click_highlight = false;
                _ig = ig;

                ItemType = MeterItemType.TUNESTEP_BUTTONS;

                base.Buttons = _console.TuneStepList.Count;
                VisibleBits = (1 << 10) - 1; //1023, 10 bits
            }
            public override int VisibleBits
            {
                get
                {
                    return _button_bits;
                }
                set
                {
                    _button_bits = value;
                    for (int n = 0; n < Buttons; n++)
                    {
                        SetVisible(0, n, (_button_bits & (1 << n)) != 0);
                    }
                    setupButtons();
                }
            }
            private void setupButtons()
            {
                if (!RebuildButtons) return;

                // copy from 0 to 1. 0 is settings bank, 1 is where renderer reads from
                for (int i = 0; i < Buttons; i++)
                {
                    SetText(1, i, _console.TuneStepList[i].Name.Replace("Hz", ""));//GetText(0, i));
                    SetFontColour(1, i, GetFontColour(0, i));
                    SetFontFamily(1, i, GetFontFamily(0, i));
                    SetFontSize(1, i, GetFontSize(0, i));
                    SetFontStyle(1, i, GetFontStyle(0, i));
                    SetUseIndicator(1, i, GetUseIndicator(0, i));
                    SetOnColour(1, i, GetOnColour(0, i));
                    SetOffColour(1, i, GetOffColour(0, i));
                    SetIndicatorWidth(1, i, GetIndicatorWidth(0, i));
                    SetEnabled(1, i, true);
                    SetVisible(1, i, GetVisible(0, i));
                    SetOn(1, i, false);

                    SetFillColour(1, i, GetFillColour(0, i));
                    SetHoverColour(1, i, GetHoverColour(0, i));
                    SetBorderColour(1, i, GetBorderColour(0, i));

                    SetClickColour(1, i, GetClickColour(0, i));

                    SetUseOffColour(1, i, GetUseOffColour(0, i));

                    SetIndicatorType(1, i, GetIndicatorType(0, i));
                }

                SetOn(1, _console.TuneStepIndex, true);

                //

                int total_buttons = 0;
                for (int i = 0; i < Buttons; i++)
                {
                    if (GetVisible(1, i)) total_buttons++;
                }

                int rows = total_buttons / Columns;
                int overflow = total_buttons % Columns;
                if (overflow > 0) rows++;

                float half_border = Border / 2f;
                float button_width = ((1f - (0.02f * 2f)) / (float)Columns) - Margin - Border;
                float button_height = ((1f - (0.02f * 2f)) / (float)Columns) * HeightRatio;

                float height = button_height * (float)rows;

                Size = new SizeF(Size.Width, height);

                float fPadY = 0.04f;
                float fHeight = 0.05f;
                _ig.Size = new SizeF(_ig.Size.Width, height + (fPadY - (fHeight * 0.75f)));
                _owningmeter.Rebuild();
            }
            public override int Columns
            {
                get { return base.Columns; }
                set
                {
                    base.Columns = value;
                    setupButtons();
                }
            }
            public override float Border
            {
                get { return base.Border; }
                set
                {
                    base.Border = value;
                    setupButtons();
                }
            }
            public override float Margin
            {
                get { return base.Margin; }
                set
                {
                    base.Margin = value;
                    setupButtons();
                }
            }
            public override float Radius
            {
                get { return base.Radius; }
                set
                {
                    base.Radius = value;
                    setupButtons();
                }
            }
            public override float HeightRatio
            {
                get { return base.HeightRatio; }
                set
                {
                    base.HeightRatio = value;
                    setupButtons();
                }
            }
            public override bool ClickHighlight
            {
                get { return _click_highlight; }
            }
            private void setupClick(bool setup)
            {
                if (setup)
                {
                    _click_timer = new System.Timers.Timer(100);
                    _click_timer.Elapsed += (sender, ee) =>
                    {
                        _click_highlight = false;
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    };
                    _click_timer.AutoReset = false;
                    _click_timer.Start();
                }
                else
                {
                    if (_click_timer != null)
                    {
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    }
                    _click_highlight = true;
                }
            }
            public override void MouseDown(MouseEventArgs e)
            {
                Debug.Print("DOWN");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;

                setupClick(false);
            }
            public override void MouseUp(MouseEventArgs e)
            {
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                if (_console == null) return;

                setupClick(true);

                int index = base.ButtonIndex;
                if (index == -1) return;

                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.TuneStepIndex = index;
                }));
            }
            public override void TuneStepIndexChanged(int old_index, int new_index)
            {
                setupButtons();
            }
        }
        internal class clsAntennaButtonBox : clsButtonBox
        {
            private clsMeter _owningmeter;
            private Band _rx1_band;
            private Band _tx_band;
            private double _vfoa_freq;
            private double _tx_freq;
            private System.Threading.Timer _timer;
            private readonly object _timerLock = new object();
            private int _button_bits;
            private bool _rxtx_swap;
            private clsItemGroup _ig;
            private bool _click_highlight;
            private System.Timers.Timer _click_timer;
            public clsAntennaButtonBox(clsMeter owningmeter, clsItemGroup ig)
            {
                _owningmeter = owningmeter;
                _click_highlight = false;
                _rx1_band = _owningmeter.BandVfoA;
                _tx_band = _owningmeter.TXBand;
                _vfoa_freq = -1;
                _tx_freq = -1;
                _rxtx_swap = false;
                _ig = ig;

                ItemType = MeterItemType.ANTENNA_BUTTONS;

                Buttons = 10;
                VisibleBits = (1 << 10) - 1; //1023, 10 bits
            }
            public override int VisibleBits
            {
                get
                {
                    return _button_bits;
                }
                set
                {
                    _button_bits = value;
                    for (int n = 0; n < Buttons; n++)
                    {
                        SetVisible(0, n, (_button_bits & (1 << n)) != 0);
                    }
                    setupButtons();
                }
            }
            private void setupButtons()
            {
                if (!RebuildButtons) return;

                if (_rx1_band == Band.FIRST || _tx_band == Band.FIRST) return;

                // copy from 0 to 1. 0 is settings bank, 1 is where renderer reads from
                for (int i = 0; i < Buttons; i++)
                {
                    SetText(1, i, GetText(0, i));
                    SetFontColour(1, i, GetFontColour(0, i));
                    SetFontFamily(1, i, GetFontFamily(0, i));
                    SetFontSize(1, i, GetFontSize(0, i));
                    SetFontStyle(1, i, GetFontStyle(0, i));
                    SetUseIndicator(1, i, GetUseIndicator(0, i));
                    SetOnColour(1, i, GetOnColour(0, i));
                    SetOffColour(1, i, GetOffColour(0, i));
                    SetIndicatorWidth(1, i, GetIndicatorWidth(0, i));
                    SetEnabled(1, i, true);
                    SetVisible(1, i, GetVisible(0, i));
                    SetOn(1, i, false);

                    SetFillColour(1, i, GetFillColour(0, i));
                    SetHoverColour(1, i, GetHoverColour(0, i));
                    SetBorderColour(1, i, GetBorderColour(0, i));

                    SetClickColour(1, i, GetClickColour(0, i));

                    SetUseOffColour(1, i, GetUseOffColour(0, i));

                    SetIndicatorType(1, i, GetIndicatorType(0, i));
                }

                Band rx_band;
                Band tx_band;

                rx_band = _rx1_band;
                tx_band = _tx_band;
                
                bool using_xvtr = ((int)rx_band >= (int)Band.VHF0 && (int)rx_band <= (int)Band.VHF13);
                bool is_xvtr_rx = false;
                if (using_xvtr)
                {
                    rx_band = _console.GetTransverterTranslatedRXBand(_vfoa_freq);
                    is_xvtr_rx = GetRXAuxState(rx_band, 2);

                    tx_band = _console.GetTransverterTranslatedRXBand(_tx_freq);
                }
                bool using_byp_ext1 = !using_xvtr && (GetRXAuxState(rx_band, 0) || GetRXAuxState(rx_band, 1));

                SetText(1, 0, "Rx Ant 1");
                SetFontColour(1, 0, System.Drawing.Color.LimeGreen);
                if (!using_byp_ext1 && !is_xvtr_rx) SetOn(1, 0, MeterManager.GetRXAntState(rx_band, 0));

                SetText(1, 1, "Rx Ant 2");
                SetFontColour(1, 1, System.Drawing.Color.LimeGreen);
                if (!using_byp_ext1 && !is_xvtr_rx) SetOn(1, 1, MeterManager.GetRXAntState(rx_band, 1));

                SetText(1, 2, "Rx Ant 3");
                SetFontColour(1, 2, System.Drawing.Color.LimeGreen);
                if (!using_byp_ext1 && !is_xvtr_rx) SetOn(1, 2, MeterManager.GetRXAntState(rx_band, 2));


                SetText(1, 3, formatAux(MeterManager.GetRXAuxName(0)));
                SetFontColour(1, 3, System.Drawing.Color.Orange);
                if (!is_xvtr_rx && using_byp_ext1) SetOn(1, 3, MeterManager.GetRXAuxState(rx_band, 0));

                SetText(1, 4, formatAux(MeterManager.GetRXAuxName(1)));
                SetFontColour(1, 4, System.Drawing.Color.Orange);
                if (!is_xvtr_rx && using_byp_ext1) SetOn(1, 4, MeterManager.GetRXAuxState(rx_band, 1));

                SetText(1, 5, formatAux(MeterManager.GetRXAuxName(2)));
                SetFontColour(1, 5, System.Drawing.Color.Orange);
                if(!using_byp_ext1 && using_xvtr) SetOn(1, 5, MeterManager.GetRXAuxState(rx_band, 2));

                int rx_ant = MeterManager.GetRXAnt(rx_band, using_xvtr);
                int tx_ant = MeterManager.GetTXAnt(tx_band);
                int rx_ant_on_tx_band = MeterManager.GetRXAnt(tx_band, false);

                SetText(1, 6, "Tx Ant 1");
                if (rx_ant_on_tx_band == 0 && GetNoTX(0))
                {
                    SetFontColour(1, 6, System.Drawing.Color.Gray);
                }
                else
                {
                    SetFontColour(1, 6, System.Drawing.Color.Red);
                    SetOn(1, 6, MeterManager.GetTXAntState(tx_band, 0));
                }                

                SetText(1, 7, "Tx Ant 2");
                if (rx_ant_on_tx_band == 1 && GetNoTX(1))
                {
                    SetFontColour(1, 7, System.Drawing.Color.Gray);
                }
                else
                {
                    SetFontColour(1, 7, System.Drawing.Color.Red);
                    SetOn(1, 7, MeterManager.GetTXAntState(tx_band, 1));
                }                

                SetText(1, 8, "Tx Ant 3");
                if (rx_ant_on_tx_band == 2 && GetNoTX(2))
                {
                    SetFontColour(1, 8, System.Drawing.Color.Gray);
                }
                else
                {
                    SetFontColour(1, 8, System.Drawing.Color.Red);
                    SetOn(1, 8, MeterManager.GetTXAntState(tx_band, 2));
                }               
                               
                if(rx_ant == tx_ant || using_xvtr || GetNoTX(tx_ant) || (rx_ant > 4))
                {
                    SetText(1, 9, "Rx Ant");
                    SetFontColour(1, 9, System.Drawing.Color.Gray);
                }
                else
                {
                    if (_rxtx_swap)
                    {
                        SetText(1, 9, "Tx Ant");
                        SetOn(1, 9, true);
                        SetFontColour(1, 9, System.Drawing.Color.Yellow);
                    }
                    else
                    {
                        SetText(1, 9, "Rx Ant");
                        SetOn(1, 9, false);
                        SetFontColour(1, 9, System.Drawing.Color.LimeGreen);
                    }
                }
                //

                int total_buttons = 0;
                for (int i = 0; i < Buttons; i++)
                {
                    if (GetVisible(1, i)) total_buttons++;
                }

                int rows = total_buttons / Columns;
                int overflow = total_buttons % Columns;
                if (overflow > 0) rows++;

                float half_border = Border / 2f;
                float button_width = ((1f - (0.02f * 2f)) / (float)Columns) - Margin - Border;
                float button_height = ((1f - (0.02f * 2f)) / (float)Columns) * HeightRatio;

                float height = button_height * (float)rows;

                Size = new SizeF(Size.Width, height);

                float fPadY = 0.04f;
                float fHeight = 0.05f;
                _ig.Size = new SizeF(_ig.Size.Width, height + (fPadY - (fHeight * 0.75f)));
                _owningmeter.Rebuild();
            }
            private string formatAux(string name)
            {
                if (name.Length < 2) return "";
                string tmp = name.ToLower();
                tmp = char.ToUpper(tmp[0]) + tmp.Substring(1);
                if (tmp == "Byps") tmp = "Bypass";
                return tmp;
            }
            public void AntennasChanged(Band rx1_band, Band tx_band, double vfoa_freq, double tx_freq, int rxtx_swap)
            {
                bool valid = false;

                bool valid_rx = (rx1_band >= Band.B160M && rx1_band <= Band.B6M) || (rx1_band >= Band.VHF0 && rx1_band <= Band.VHF13);
                bool valid_tx = (tx_band >= Band.B160M && tx_band <= Band.B6M) || (tx_band >= Band.VHF0 && tx_band <= Band.VHF13);

                valid = valid_rx || valid_tx || (rx1_band == Band.FIRST && tx_band == Band.FIRST && vfoa_freq == -1 && tx_freq == -1)
                    || rx1_band == Band.WWV;

                if (!valid_rx && vfoa_freq != -1)
                {
                    rx1_band = Alex.AntBandFromFreq(vfoa_freq);
                    valid = true;
                }
                if (!valid_tx && tx_freq != -1)
                {
                    tx_band = Alex.AntBandFromFreq(tx_freq);
                    valid = true;
                }

                if (valid)
                {
                    if (rx1_band != Band.FIRST && rx1_band != _rx1_band)
                    {
                        _rx1_band = rx1_band;
                    }
                    if (tx_band != Band.FIRST && tx_band != _tx_band)
                    {
                        _tx_band = tx_band;
                    }
                    if (vfoa_freq > -1 && vfoa_freq != _vfoa_freq)
                    {
                        _vfoa_freq = vfoa_freq;
                    }
                    if (tx_freq > -1 && tx_freq != _tx_freq)
                    {
                        _tx_freq = tx_freq;
                    }
                    int rxtx_s = _rxtx_swap ? 2 : 1;
                    if (rxtx_swap > 0 && rxtx_swap != rxtx_s)
                    {
                        _rxtx_swap = rxtx_swap == 2 ? true : false;
                    }
                }

                // the above can happen very fast, but only after it stops happening for 250ms, then call the setupbuttons()
                lock (_timerLock)
                {
                    if (_timer == null)
                        _timer = new System.Threading.Timer(_ => setupButtons(), null, 250, Timeout.Infinite);
                    else
                        _timer.Change(100, Timeout.Infinite);
                }
            }
            public override bool ClickHighlight
            {
                get { return _click_highlight; }
            }
            private void setupClick(bool setup)
            {
                if (setup)
                {
                    _click_timer = new System.Timers.Timer(100);
                    _click_timer.Elapsed += (sender, ee) =>
                    {
                        _click_highlight = false;
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    };
                    _click_timer.AutoReset = false;
                    _click_timer.Start();
                }
                else
                {
                    if (_click_timer != null)
                    {
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    }
                    _click_highlight = true;
                }
            }
            public override void MouseDown(MouseEventArgs e)
            {
                Debug.Print("DOWN");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;

                setupClick(false);
            }
            public override void MouseUp(MouseEventArgs e)
            {
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                if (_console == null) return;
                if (_console.IsSetupFormNull) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                setupClick(true);

                if (index >= 0 && index <= 2)
                    setRXAntenna(index, _rx1_band);

                if (index >= 3 && index <= 4) // ignore xvtr for now
                    setAuxAntenna(index, _rx1_band, index == 3, index == 4, index == 5);

                if (index >= 6 && index <= 8)
                    setTXAntenna(index - 6, _rx1_band);

                if (index == 9)
                    toggleTxRxAnt();
            }
            private void toggleTxRxAnt()
            {
                if (_console == null) return;
                if (_console.IsSetupFormNull) return;
                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.ToggleRxTxAnt();
                }));
            }
            private void setRXAntenna(int antenna, Band b)
            {
                if (_console == null) return;
                if (_console.IsSetupFormNull) return;

                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.SetupForm.SetRXAntenna(antenna + 1, b);
                }));
            }
            private void setAuxAntenna(int antenna, Band b, bool byp, bool ext1, bool xvtr)
            {
                if (_console == null) return;
                if (_console.IsSetupFormNull) return;

                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.SetupForm.SetAuxAntenna(antenna + 1, b, byp, ext1);
                }));
            }
            private void setTXAntenna(int antenna, Band b)
            {
                if (_console == null) return;
                if (_console.IsSetupFormNull) return;

                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.SetupForm.SetTXAntenna(antenna + 1, b);
                }));
            }
            public override int Columns
            {
                get { return base.Columns; }
                set
                {
                    base.Columns = value;
                    setupButtons();
                }
            }
            public override float Border
            {
                get { return base.Border; }
                set
                {
                    base.Border = value;
                    setupButtons();
                }
            }
            public override float Margin
            {
                get { return base.Margin; }
                set
                {
                    base.Margin = value;
                    setupButtons();
                }
            }
            public override float Radius
            {
                get { return base.Radius; }
                set
                {
                    base.Radius = value;
                    setupButtons();
                }
            }
            public override float HeightRatio
            {
                get { return base.HeightRatio; }
                set
                {
                    base.HeightRatio = value;
                    setupButtons();
                }
            }
        }
        internal class clsModeButtonBox : clsButtonBox
        {
            private DSPMode _mode;
            clsMeter _owningmeter;
            private bool _click_highlight;
            private System.Timers.Timer _click_timer;

            public clsModeButtonBox(clsMeter owningmeter)
            {
                _owningmeter = owningmeter;
                _click_highlight = false;

                if (_owningmeter.RX == 1)
                    _mode = _owningmeter.ModeVfoA;
                else if (_owningmeter.RX == 2)
                    _mode = _owningmeter.ModeVfoB;

                ItemType = MeterItemType.MODE_BUTTONS;

                Buttons = 12;

                setupButtons();
            }
            public override void ModeChanged(DSPMode oldMode, DSPMode newMode)
            {
                DSPMode old = _mode;
                _mode = newMode;

                // just set the mode
                int index = (int)_mode - (int)DSPMode.LSB;
                int old_index = (int)old - (int)DSPMode.LSB;

                SetOn(1, old_index, false);
                SetOn(1, index, true);
            }
            private void setupButtons()
            {
                if (!RebuildButtons) return;

                // copy from 0 to 1. 0 is settings bank, 1 is where renderer reads from
                for (int i = 0; i < Buttons; i++)
                {
                    SetText(1, i, GetText(0, i));
                    SetFontColour(1, i, GetFontColour(0, i));
                    SetFontFamily(1, i, GetFontFamily(0, i));
                    SetFontSize(1, i, GetFontSize(0, i));
                    SetFontStyle(1, i, GetFontStyle(0, i));
                    SetUseIndicator(1, i, GetUseIndicator(0, i));
                    SetOnColour(1, i, GetOnColour(0, i));
                    SetOffColour(1, i, GetOffColour(0, i));
                    SetIndicatorWidth(1, i, GetIndicatorWidth(0, i));
                    SetEnabled(1, i, true);
                    SetVisible(1, i, true);
                    SetOn(1, i, false);

                    SetFillColour(1, i, GetFillColour(0, i));
                    SetHoverColour(1, i, GetHoverColour(0, i));
                    SetBorderColour(1, i, GetBorderColour(0, i));

                    SetClickColour(1, i, GetClickColour(0, i));

                    SetUseOffColour(1, i, GetUseOffColour(0, i));

                    SetIndicatorType(1, i, GetIndicatorType(0, i));
                }
                //

                int start_mode = (int)DSPMode.LSB;
                int end_mode = (int)DSPMode.DRM;
                System.Drawing.Color text_color = GetFontColour(0, 0);//System.Drawing.Color.White;

                int modes = end_mode - start_mode + 1;

                for (int i = 0; i < Buttons; i++)
                {
                    if (i < modes)
                    {
                        DSPMode b = (DSPMode)(start_mode + i);
                        string mode_text = b.ToString();

                        SetText(1, i, mode_text);

                        if (GetEnabled(1, i))
                            SetFontColour(1, i, text_color);
                        else
                            SetFontColour(1, i, System.Drawing.Color.FromArgb(255, (int)(text_color.R * 0.3f), (int)(text_color.G * 0.3f), (int)(text_color.B * 0.3f)));

                        SetFontSize(1, i, 18f);

                        SetOn(1, i, b == _mode);
                    }
                }

                int rows = Buttons / Columns;
                int overflow = Buttons % Columns;
                if (overflow > 0) rows++;

                float half_border = Border / 2f;
                float button_width = ((1f - (0.02f * 2f)) / (float)Columns) - Margin - Border;
                float button_height = ((1f - (0.02f * 2f)) / (float)Columns) * HeightRatio;

                float height = button_height * (float)rows;

                Size = new SizeF(Size.Width, height);
            }
            public override bool ClickHighlight
            {
                get { return _click_highlight; }
            }
            private void setupClick(bool setup)
            {
                if (setup)
                {
                    _click_timer = new System.Timers.Timer(100);
                    _click_timer.Elapsed += (sender, ee) =>
                    {
                        _click_highlight = false;
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    };
                    _click_timer.AutoReset = false;
                    _click_timer.Start();
                }
                else
                {
                    if (_click_timer != null)
                    {
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    }
                    _click_highlight = true;
                }
            }
            public override void MouseDown(MouseEventArgs e)
            {
                Debug.Print("DOWN");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;

                setupClick(false);
            }
            public override void MouseUp(MouseEventArgs e)
            {
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                if (_console == null) return;
                int index = base.ButtonIndex;
                if (index == -1) return;

                setupClick(true);

                DSPMode m = (DSPMode)((int)DSPMode.LSB + index);                

                setMode(m);
            }
            private bool abortForLockedVFO()
            {
                bool abort = false;
                if (_owningmeter.RX == 1)
                {
                    return _owningmeter.VFOALock;
                }
                else if (_owningmeter.RX == 2)
                {
                    return _owningmeter.VFOBLock;
                }
                return abort;
            }
            private void setMode(DSPMode m)
            {
                if (abortForLockedVFO()) return;
                if (m == DSPMode.FIRST) return;

                if (_owningmeter.RX == 2)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.RX2DSPMode = m;
                    }));
                }
                else
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.RX1DSPMode = m;
                    }));
                }
            }

            public override int Columns
            {
                get { return base.Columns; }
                set
                {
                    base.Columns = value;
                    setupButtons();
                }
            }
            public override float Border
            {
                get { return base.Border; }
                set
                {
                    base.Border = value;
                    setupButtons();
                }
            }
            public override float Margin
            {
                get { return base.Margin; }
                set
                {
                    base.Margin = value;
                    setupButtons();
                }
            }
            public override float Radius
            {
                get { return base.Radius; }
                set
                {
                    base.Radius = value;
                    setupButtons();
                }
            }
            public override float HeightRatio
            {
                get { return base.HeightRatio; }
                set
                {
                    base.HeightRatio = value;
                    setupButtons();
                }
            }
        }
        internal class clsBandButtonBox : clsButtonBox
        {
            private BandGroups _button_bands;
            private Band _band;
            private bool _force_update;
            clsMeter _owningmeter;
            private bool _click_highlight;
            private System.Timers.Timer _click_timer;

            public clsBandButtonBox(clsMeter owningmeter)
            {
                _owningmeter = owningmeter;

                if (_owningmeter.RX == 1)
                {
                    _band = _owningmeter.BandVfoA;
                    //use panels
                    if(_console != null)
                    {
                        if (_console.BandGENSelected)
                            _button_bands = BandGroups.GEN;
                        else if (_console.BandHFSelected)
                            _button_bands = BandGroups.HF;
                        else if (_console.BandVHFSelected)
                            _button_bands = BandGroups.VHF;
                        else
                            _button_bands = _owningmeter.GetBandGroupFromBand(_band);
                    }
                    else
                    {
                        _button_bands = _owningmeter.GetBandGroupFromBand(_band);
                    }
                } 
                else if (_owningmeter.RX == 2)
                {
                    _band = _owningmeter.BandVfoB;
                    _button_bands = _owningmeter.GetBandGroupFromBand(_band);
                }

                _click_highlight = false;
                _force_update = false;

                ItemType = MeterItemType.BAND_BUTTONS;

                Buttons = 15;

                setupButtons();
            }
            public override void BandPanelsChanged(bool gen, bool hf, bool vhf)
            {
                if (gen)
                    ButtonBands = BandGroups.GEN;
                else if (hf)
                    ButtonBands = BandGroups.HF;
                else if (vhf)
                    ButtonBands = BandGroups.VHF;
            }
            public void VHFUpdate(int index, bool enabled, string text)
            {
                if (_button_bands != BandGroups.VHF) return;
                if (index < 0 || index > 14) return;

                SetEnabled(1, index, enabled);

                System.Drawing.Color text_color = BandStackManager.BandToColour(Band.VHF0);
                if (enabled)
                    SetFontColour(1, index, text_color);
                else
                    SetFontColour(1, index, System.Drawing.Color.FromArgb(255, (int)(text_color.R * 0.3f), (int)(text_color.G * 0.3f), (int)(text_color.B * 0.3f)));

                SetText(1, index, text);
            }
            public override void BandChanged(Band oldBand, Band newBand)
            {
                Band old = _band;
                _band = newBand;

                BandGroups bands = _owningmeter.GetBandGroupFromBand(_band);
                BandGroups old_bands = _owningmeter.GetBandGroupFromBand(old);

                if (bands != old_bands)
                {
                    _force_update = true;
                    ButtonBands = _owningmeter.GetBandGroupFromBand(newBand);
                    _force_update = false;
                }
                else
                {
                    // just set the band
                    int index = -1;
                    int old_index = -1;
                    switch (_button_bands)
                    {
                        case BandGroups.GEN:
                            index = (int)_band - (int)Band.BLMF;
                            old_index = (int)old - (int)Band.BLMF;
                            break;
                        case BandGroups.HF:
                            index = (int)_band - (int)Band.B160M;
                            old_index = (int)old - (int)Band.B160M;
                            break;
                        case BandGroups.VHF:
                            index = (int)_band - (int)Band.VHF0;
                            old_index = (int)old - (int)Band.VHF0;
                            break;
                    }
                    if(index != -1 && old_index != -1)
                    {
                        SetOn(1, old_index, false);
                        if (newBand != Band.WWV)
                            SetOn(1, index, true);
                    }
                }
            }
            public BandGroups ButtonBands
            {
                get { return _button_bands; }
                set 
                {
                    BandGroups old = _button_bands;
                    _button_bands = value;

                    if(old != _button_bands || _force_update)
                        setupButtons();
                }
            }
            private void setupButtons()
            {
                if (!RebuildButtons) return;

                // copy from 0 to 1. 0 is settings bank, 1 is where renderer reads from
                for (int i = 0; i < Buttons; i++)
                {
                    SetText(1, i, GetText(0, i));
                    SetFontColour(1, i, GetFontColour(0, i));
                    SetFontFamily(1, i, GetFontFamily(0, i));
                    SetFontSize(1, i, GetFontSize(0, i));
                    SetFontStyle(1, i, GetFontStyle(0, i));                    
                    SetUseIndicator(1, i, GetUseIndicator(0, i));
                    SetOnColour(1, i, GetOnColour(0, i));
                    SetOffColour(1, i, GetOffColour(0, i));
                    SetIndicatorWidth(1, i, GetIndicatorWidth(0, i));
                    SetEnabled(1, i, true);
                    SetVisible(1, i, true);
                    SetOn(1, i, false);

                    SetFillColour(1, i, GetFillColour(0, i));
                    SetHoverColour(1, i, GetHoverColour(0, i));
                    SetBorderColour(1, i, GetBorderColour(0, i));

                    SetClickColour(1, i, GetClickColour(0, i));

                    SetUseOffColour(1, i, GetUseOffColour(0, i));

                    SetIndicatorType(1, i, GetIndicatorType(0, i));
                }
                //

                int start_band = 0;
                int end_band = 0;
                System.Drawing.Color text_color = System.Drawing.Color.White;
                switch (_button_bands)
                {
                    case BandGroups.GEN:
                        start_band = (int)Band.BLMF;
                        end_band = (int)Band.B11M;
                        text_color = System.Drawing.Color.Yellow;

                        SetText(1, 14, "HF");
                        SetFontColour(1, 14, System.Drawing.Color.Red);
                        SetUseIndicator(1, 14, false);
                        SetUseOffColour(1, 14, false);
                        SetOn(1, 14, false);
                        break;
                    case BandGroups.HF:
                        start_band = (int)Band.B160M;
                        end_band = (int)Band.B6M;
                        text_color = GetFontColour(0, 0);//BandStackManager.BandToColour(Band.B160M);

                        SetText(1, 11, "");
                        SetHoverColour(1, 11, System.Drawing.Color.Transparent);
                        SetUseIndicator(1, 12, false);                        
                        SetOn(1, 11, false);
                        SetUseOffColour(1, 11, false);
                        //SetBorderColour(1, 11, System.Drawing.Color.Transparent);
                        //SetFillColour(1, 11, System.Drawing.Color.Transparent);

                        SetText(1, 12, "VHF");
                        SetFontColour(1, 12, BandStackManager.BandToColour(Band.VHF0));
                        SetUseIndicator(1, 12, false);
                        SetOn(1, 12, false);
                        SetUseOffColour(1, 12, false);

                        SetText(1, 13, "WWV");
                        SetFontColour(1, 13, System.Drawing.Color.LightGreen);
                        SetUseIndicator(1, 13, false);
                        SetOn(1, 13, false);
                        SetUseOffColour(1, 13, false);

                        SetText(1, 14, "SWL");
                        SetFontColour(1, 14, BandStackManager.BandToColour(Band.B120M));
                        SetUseIndicator(1, 14, false);
                        SetOn(1, 14, false);
                        SetUseOffColour(1, 14, false);
                        break;
                    case BandGroups.VHF:
                        start_band = (int)Band.VHF0;
                        end_band = (int)Band.VHF13;
                        text_color = BandStackManager.BandToColour(Band.VHF0);

                        SetText(1, 14, "HF");
                        SetFontColour(1, 14, System.Drawing.Color.Red);
                        SetUseIndicator(1, 14, false);
                        SetOn(1, 14, false);
                        SetUseOffColour(1, 14, false);
                        break;
                }

                int bands = end_band - start_band + 1;
                
                for (int i = 0; i < Buttons; i++)
                {
                    if(i < bands)
                    {
                        string band_text = "";
                        Band b = (Band)(start_band + i);

                        if (_button_bands == BandGroups.VHF)
                        {
                            SetEnabled(1, i, _console.GetVHFEnabled(i));

                            band_text = _console.GetVHFText(i).Left(3);
                            if (string.IsNullOrEmpty(band_text))
                            {
                                band_text = BandStackManager.BandToString(b);
                                band_text = band_text.Substring(3);
                            }
                        }
                        else
                        {
                            if (b == Band.BLMF)
                            {
                                band_text = "L/MW";
                            }
                            else
                            {
                                band_text = BandStackManager.BandToString(b);
                                band_text = band_text.Left(band_text.Length - 1);
                            }
                        }

                        SetText(1, i, band_text);

                        if (GetEnabled(1, i))
                            SetFontColour(1, i, text_color);
                        else
                            SetFontColour(1, i, System.Drawing.Color.FromArgb(255, (int)(text_color.R * 0.3f), (int)(text_color.G * 0.3f), (int)(text_color.B * 0.3f)));

                        SetFontSize(1, i, 18f);

                        SetOn(1, i, b == _band);
                    }
                }

                int rows = Buttons / Columns;
                int overflow = Buttons % Columns;
                if (overflow > 0) rows++;

                float half_border = Border / 2f;
                float button_width = ((1f - (0.02f * 2f)) / (float)Columns) - Margin - Border;
                float button_height = ((1f - (0.02f * 2f)) / (float)Columns) * HeightRatio;

                float height = button_height * (float)rows;

                Size = new SizeF(Size.Width, height);
            }
            public override bool ClickHighlight
            {
                get { return _click_highlight; }
            }
            private void setupClick(bool setup)
            {
                if (setup)
                {
                    _click_timer = new System.Timers.Timer(100);
                    _click_timer.Elapsed += (sender, ee) =>
                    {
                        _click_highlight = false;
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    };
                    _click_timer.AutoReset = false;
                    _click_timer.Start();
                }
                else
                {
                    if (_click_timer != null)
                    {
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    }
                    _click_highlight = true;
                }
            }
            public override void MouseDown(MouseEventArgs e)
            {
                Debug.Print("DOWN");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;

                setupClick(false);
            }
            public override void MouseUp(MouseEventArgs e)
            {
                Debug.Print("UP");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                setupClick(true);

                if (_console == null) return;
                int index = base.ButtonIndex;
                if (index == -1) return;

                Band b = Band.FIRST;
                switch (ButtonBands)
                {
                    case BandGroups.GEN:
                        {
                            if (index == 14) // HF button
                            {
                                ButtonBands = BandGroups.HF;
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, true, false);
                                return;
                            }
                            b = (Band)((int)Band.BLMF + index);
                            if (index > 13) return;
                            break;
                        }
                    case BandGroups.HF:
                        {
                            switch (index)
                            {
                                case 12: //VHF
                                    ButtonBands = BandGroups.VHF;
                                    _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, false, true);
                                    return;
                                case 13: //WVV
                                    setBand(Band.WWV);
                                    // dont need to set band panel, as will happen due to band change
                                    return;
                                case 14: //SWL
                                    ButtonBands = BandGroups.GEN;
                                    _owningmeter.SetBandPanel(_console, _owningmeter.RX, true, false, false);
                                    return;
                            }
                            if (index > 10) return;
                            b = (Band)((int)Band.B160M + index);
                            break;
                        }
                    case BandGroups.VHF:
                        {
                            if (index == 14) // HF button
                            {
                                ButtonBands = BandGroups.HF;
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, true, false);
                                return;
                            }
                            if (index > 13 || !base.GetEnabled(1, index)) return;
                            b = (Band)((int)Band.VHF0 + index);
                            break;
                        }
                }

                if (e.Button == MouseButtons.Right && base.GetEnabled(1, index))
                {
                    if (_console != null)
                    {
                        _console.BeginInvoke(new MethodInvoker(() =>
                        {
                            _console.PopupBandstack(_owningmeter.RX, b, MeterManager.IsOnTop(_owningmeter.ID));
                        }));
                    }
                    return;
                }

                setBand(b);
            }
            private void setBand(Band b)
            {
                if(abortForLockedVFO()) return;
                if (b == Band.FIRST) return;

                if (_owningmeter.RX == 1)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.BandPreChangeHandlers?.Invoke(1, b);
                    }));
                }
                else if(_owningmeter.RX == 2)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.SetupRX2Band(b, false);
                    }));
                }
            }
            private bool abortForLockedVFO()
            {
                bool abort = false;
                if (_owningmeter.RX == 1)
                {
                    return _owningmeter.VFOALock;
                }
                else if (_owningmeter.RX == 2)
                {
                    return _owningmeter.VFOBLock;
                }
                return abort;
            }
            public override int Columns
            {
                get { return base.Columns; }
                set
                {
                    base.Columns = value;
                    setupButtons();
                }
            }
            public override float Border
            {
                get { return base.Border; }
                set
                {
                    base.Border = value;
                    setupButtons();
                }
            }
            public override float Margin
            {
                get { return base.Margin; }
                set
                {
                    base.Margin = value;
                    setupButtons();
                }
            }
            public override float Radius
            {
                get { return base.Radius; }
                set
                {
                    base.Radius = value;
                    setupButtons();
                }
            }
            public override float HeightRatio
            {
                get { return base.HeightRatio; }
                set
                {
                    base.HeightRatio = value;
                    setupButtons();
                }
            }
        }
        internal class clsDiscordButtonBox : clsButtonBox
        {
            private BandGroups _button_bands;
            private Band _band;
            private bool _force_update;
            clsMeter _owningmeter;
            private bool _ready;
            private bool _click_highlight;
            private System.Timers.Timer _click_timer;
            public clsDiscordButtonBox(clsMeter owningmeter)
            {
                _ready = ThetisBotDiscord.IsReady;
                _owningmeter = owningmeter;
                _click_highlight = false;
                _force_update = false;

                ItemType = MeterItemType.DISCORD_BUTTONS;

                Buttons = 9;

                setupButtons();

                ThetisBotDiscord.ReadyHandlers += OnReady;
                ThetisBotDiscord.DisconnectedHandlers += OnDisconnected;
            }
            private void OnReady()
            {
                for (int i = 0; i < Buttons; i++)
                {
                    System.Drawing.Color text_color = GetFontColour(0, i);
                    SetEnabled(1, i, true);
                    SetFontColour(1, i, text_color);
                }
                _ready = true;
            }
            private void OnDisconnected()
            {
                
                for (int i = 0; i < Buttons; i++)
                {
                    System.Drawing.Color text_color = GetFontColour(0, i);
                    SetEnabled(1, i, false);
                    SetFontColour(1, i, System.Drawing.Color.FromArgb(255, (int)(text_color.R * 0.3f), (int)(text_color.G * 0.3f), (int)(text_color.B * 0.3f)));
                }
                _ready = false;
            }
            private void setupButtons()
            {
                if (!RebuildButtons) return;

                // copy from 0 to 1. 0 is settings bank, 1 is where renderer reads from
                for (int i = 0; i < Buttons; i++)
                {
                    SetText(1, i, GetText(0, i));
                    if (_ready)
                    {
                        SetFontColour(1, i, GetFontColour(0, i));
                    }
                    else
                    {
                        System.Drawing.Color text_color = GetFontColour(0, i);
                        SetFontColour(1, i, System.Drawing.Color.FromArgb(255, (int)(text_color.R * 0.3f), (int)(text_color.G * 0.3f), (int)(text_color.B * 0.3f)));
                    }
                    SetFontFamily(1, i, GetFontFamily(0, i));
                    SetFontSize(1, i, GetFontSize(0, i));
                    SetFontStyle(1, i, GetFontStyle(0, i));
                    SetUseIndicator(1, i, GetUseIndicator(0, i));
                    SetOnColour(1, i, GetOnColour(0, i));
                    SetOffColour(1, i, GetOffColour(0, i));
                    SetIndicatorWidth(1, i, GetIndicatorWidth(0, i));
                    SetEnabled(1, i, _ready);
                    SetVisible(1, i, true);
                    SetOn(1, i, false);

                    SetFillColour(1, i, GetFillColour(0, i));
                    SetHoverColour(1, i, GetHoverColour(0, i));
                    SetBorderColour(1, i, GetBorderColour(0, i));

                    SetClickColour(1, i, GetClickColour(0, i));

                    SetUseOffColour(1, i, GetUseOffColour(0, i));

                    SetIndicatorType(1, i, GetIndicatorType(0, i));
                }
                //

                SetText(1, 0, "Active");
                SetText(1, 1, "Away");
                SetText(1, 2, "Double");
                SetText(1, 3, "CQ");
                SetText(1, 4, "QRV");
                SetText(1, 5, "QRT");
                SetText(1, 6, "Version");
                SetText(1, 7, " Net\nStart");
                SetText(1, 8, " Net\nFinish");

                int rows = Buttons / Columns;
                int overflow = Buttons % Columns;
                if (overflow > 0) rows++;

                float half_border = Border / 2f;
                float button_width = ((1f - (0.02f * 2f)) / (float)Columns) - Margin - Border;
                float button_height = ((1f - (0.02f * 2f)) / (float)Columns) * HeightRatio;

                float height = button_height * (float)rows;

                Size = new SizeF(Size.Width, height);
            }
            private string formatNumber(double number)
            {
                string numberString = number.ToString("F6", CultureInfo.InvariantCulture);
                int decimalPointIndex = numberString.IndexOf('.');
                if (decimalPointIndex != -1 && numberString.Length > decimalPointIndex + 3)
                {
                    numberString = numberString.Insert(decimalPointIndex + 4, ".");
                }
                return numberString;
            }
            public override bool ClickHighlight
            {
                get { return _click_highlight; }
            }
            private void setupClick(bool setup)
            {
                if (setup)
                {
                    _click_timer = new System.Timers.Timer(100);
                    _click_timer.Elapsed += (sender, ee) =>
                    {
                        _click_highlight = false;
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    };
                    _click_timer.AutoReset = false;
                    _click_timer.Start();
                }
                else
                {
                    if (_click_timer != null)
                    {
                        _click_timer.Stop();
                        _click_timer.Dispose();
                    }
                    _click_highlight = true;
                }
            }
            public override void MouseDown(MouseEventArgs e)
            {
                Debug.Print("DOWN");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;

                setupClick(false);               
            }
            public override void MouseUp(MouseEventArgs e)
            {
                Debug.Print("UP");
                if (FadeOnRx && !_owningmeter.MOX) return;
                if (FadeOnTx && _owningmeter.MOX) return;

                setupClick(true);

                if (_console == null) return;
                int index = base.ButtonIndex;
                if (index == -1) return;

                if (!GetEnabled(1, index)) return;                

                double freq;
                if (!_owningmeter.RX2Enabled)
                {
                    if (_owningmeter.TXVFOb)
                        freq = _owningmeter.VfoB;
                    else
                        freq = _owningmeter.VfoA;
                }
                else
                {
                    if (_owningmeter.TXVFOb)
                    {
                        freq = _owningmeter.VfoB;
                    }
                    else
                    {
                        if (_owningmeter.Split)
                            freq = _owningmeter.VfoSub;
                        else
                            freq = _owningmeter.VfoA;
                    }
                }

                switch (index)
                {
                    case 0: // active
                        sendMsg("Is Active");
                        break;
                    case 1: // away
                        sendMsg("Is Away");
                        break;
                    case 2:
                        sendMsg("Someone is doubling !");
                        break;
                    case 3:
                        sendMsg("Is calling CQ : " + formatNumber(freq) + " MHz");
                        break;
                    case 4:
                        sendMsg("Is listening : " + formatNumber(_owningmeter.VfoA) + " MHz");
                        break;
                    case 5:
                        sendMsg("Has gone QRT");
                        break;
                    case 6:
                        sendMsg("Is running Thetis v" + Common.GetVerNum(true, true));
                        break;
                    case 7:
                        sendMsg("Is starting a net on : " + formatNumber(freq) + " MHz");
                        break;
                    case 8:
                        sendMsg("Is ending a net on : " + formatNumber(freq) + " MHz");
                        break;
                }
            }
            private void sendMsg(string msg)
            {
                Task.Run(async () =>
                {
                    ulong chan = 1297325528336044144; // #thetis-bot
                    await ThetisBotDiscord.SendMessage(msg, chan);
                });
            }
            public override int Columns
            {
                get { return base.Columns; }
                set
                {
                    base.Columns = value;
                    setupButtons();
                }
            }
            public override float Border
            {
                get { return base.Border; }
                set
                {
                    base.Border = value;
                    setupButtons();
                }
            }
            public override float Margin
            {
                get { return base.Margin; }
                set
                {
                    base.Margin = value;
                    setupButtons();
                }
            }
            public override float Radius
            {
                get { return base.Radius; }
                set
                {
                    base.Radius = value;
                    setupButtons();
                }
            }
            public override float HeightRatio
            {
                get { return base.HeightRatio; }
                set
                {
                    base.HeightRatio = value;
                    setupButtons();
                }
            }
        }
        internal class clsButtonBox : clsMeterItem
        {
            public enum IndicatorType
            {
                RING = 0,
                BAR_LEFT,
                BAR_RIGHT,
                BAR_BOTTOM,
                BAR_TOP,
                DOT_LEFT,
                DOT_RIGHT,
                DOT_BOTTOM,
                DOT_TOP,
                DOT_TOP_LEFT,
                DOT_TOP_RIGHT,
                DOT_BOTTOM_LEFT,
                DOT_BOTTOM_RIGHT,

                LAST = 99
            }
            private int _number_of_buttons;
            private int _columns;
            private float _margin;
            private float _border_width;
            private float _radius;
            private float _height_ratio; // 0 to 1.0

            private float _font_scale;
            private float _font_shift_x;
            private float _font_shift_y;

            private System.Drawing.Color[][] _fill_colour;
            private System.Drawing.Color[][] _hover_colour;
            private System.Drawing.Color[][] _border_colour;

            private System.Drawing.Color[][] _click_colour;

            private bool[][] _use_off_colour;
            private bool[][] _use_indicator;
            private float[][] _indicator_width;

            private System.Drawing.Color[][] _on_colour;
            private System.Drawing.Color[][] _off_colour;
            private bool[][] _on;

            private string[][] _fontFamily;
            private FontStyle[][] _fontStyle;
            private float[][] _fontSize;
            private System.Drawing.Color[][] _font_colour;

            private string[][] _text;

            private bool[][] _enabled;

            private IndicatorType[][] _indicator_type;

            private bool[][] _visible;

            private int _button_index;
            private bool _rebuild_buttons;

            public clsButtonBox()
            {
                _number_of_buttons = 1;
                _columns = 1;
                _margin = 0;
                _border_width = 0.005f;
                _radius = 0;
                _height_ratio = 1;
                _button_index = -1;

                _font_scale = 1f;
                _font_shift_x = 0f;
                _font_shift_y = 0f;

                _rebuild_buttons = true;

                UpdateInterval = 50;

                setupArrays();
            }
            private void setupArrays()
            {
                _fill_colour = new System.Drawing.Color[2][];
                _hover_colour = new System.Drawing.Color[2][];
                _border_colour = new System.Drawing.Color[2][];

                _click_colour = new System.Drawing.Color[2][];

                _use_off_colour = new bool[2][];
                _use_indicator = new bool[2][];
                _indicator_width = new float[2][];

                _on_colour = new System.Drawing.Color[2][];
                _off_colour = new System.Drawing.Color[2][];
                _on = new bool[2][];

                _fontFamily = new string[2][];
                _fontStyle = new FontStyle[2][];
                _fontSize = new float[2][];
                _font_colour = new System.Drawing.Color[2][];

                _text = new string[2][];

                _enabled = new bool[2][];
                _visible = new bool[2][];

                _indicator_type = new IndicatorType[2][];

                // 0 is settings, 1 is active
                for (int n = 0; n < 2; n++)
                {
                    _fill_colour[n] = new System.Drawing.Color[_number_of_buttons];
                    _hover_colour[n] = new System.Drawing.Color[_number_of_buttons];
                    _border_colour[n] = new System.Drawing.Color[_number_of_buttons];

                    _click_colour[n] = new System.Drawing.Color[_number_of_buttons];

                    _use_off_colour[n] = new bool[_number_of_buttons];
                    _use_indicator[n] = new bool[_number_of_buttons];
                    _indicator_width[n] = new float[_number_of_buttons];

                    _on_colour[n] = new System.Drawing.Color[_number_of_buttons];
                    _off_colour[n] = new System.Drawing.Color[_number_of_buttons];
                    _on[n] = new bool[_number_of_buttons];

                    _fontFamily[n] = new string[_number_of_buttons];
                    _fontStyle[n] = new FontStyle[_number_of_buttons];
                    _fontSize[n] = new float[_number_of_buttons];
                    _font_colour[n] = new System.Drawing.Color[_number_of_buttons];

                    _text[n] = new string[_number_of_buttons];

                    _enabled[n] = new bool[_number_of_buttons];
                    _visible[n] = new bool[_number_of_buttons];

                    _indicator_type[n] = new IndicatorType[_number_of_buttons];

                    for (int b = 0; b < _number_of_buttons; b++) {
                        _fill_colour[n][b] = System.Drawing.Color.Black;
                        _hover_colour[n][b] = System.Drawing.Color.LightGray;
                        _border_colour[n][b] = System.Drawing.Color.White;

                        _click_colour[n][b] = System.Drawing.Color.Orange;

                        _use_off_colour[n][b] = false;
                        _use_indicator[n][b] = false;
                        _indicator_width[n][b] = 0.005f;

                        _on_colour[n][b] = System.Drawing.Color.CornflowerBlue;
                        _off_colour[n][b] = System.Drawing.Color.Black;
                        _on[n][b] = false;

                        _fontFamily[n][b] = "Trebuchet MS";
                        _fontStyle[n][b] = FontStyle.Regular;
                        _fontSize[n][b] = 18f;
                        _font_colour[n][b] = System.Drawing.Color.White;

                        _text[n][b] = "";

                        _enabled[n][b] = true;
                        _visible[n][b] = true;

                        _indicator_type[n][b] = IndicatorType.RING;
                    }
                }
            }
            public bool RebuildButtons
            {
                get { return _rebuild_buttons; }
                set { _rebuild_buttons = value; }
            }
            public float FontScale
            {
                get { return _font_scale; }
                set { _font_scale = value; }
            }
            public float FontShiftX
            {
                get { return _font_shift_x; }
                set { _font_shift_x = value; }
            }
            public float FontShiftY
            {
                get { return _font_shift_y; }
                set { _font_shift_y = value; }
            }
            public int ButtonIndex
            {
                get { return _button_index; }
                set { _button_index = value; }
            }
            public int Buttons
            {
                get { return _number_of_buttons; }
                set 
                { 
                    _number_of_buttons = value;
                    setupArrays();
                }
            }
            public virtual int VisibleBits
            {
                get { return int.MaxValue; }
                set { }
            }
            public virtual int Columns
            {
                get { return _columns; }
                set { _columns = value; }
            }
            public virtual float Margin
            {
                get { return _margin; }
                set { _margin = value; }
            }
            public virtual float Border
            {
                get { return _border_width; }
                set { _border_width = value; }
            }
            public virtual float Radius
            {
                get { return _radius; }
                set { _radius = value; }
            }
            public virtual float HeightRatio
            {
                get { return _height_ratio; }
                set { _height_ratio = value; }
            }
            public void SetFillColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _fill_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetFillColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _fill_colour[bank][button];
            }
            public void SetHoverColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _hover_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetHoverColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _hover_colour[bank][button];
            }
            public void SetClickColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _click_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetClickColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _click_colour[bank][button];
            }
            public void SetBorderColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _border_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetBorderColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _border_colour[bank][button];
            }
            public void SetUseOffColour(int bank, int button, bool use)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _use_off_colour[bank][button] = use;
            }
            public bool GetUseOffColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return false;
                return _use_off_colour[bank][button];
            }
            public void SetUseIndicator(int bank, int button, bool use)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _use_indicator[bank][button] = use;
            }
            public bool GetUseIndicator(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return false;
                return _use_indicator[bank][button];
            }
            public void SetOn(int bank, int button, bool on)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _on[bank][button] = on;
            }
            public bool GetOn(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return false;
                return _on[bank][button];
            }
            public void SetIndicatorWidth(int bank, int button, float width)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _indicator_width[bank][button] = width;
            }
            public float GetIndicatorWidth(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return 0;
                return _indicator_width[bank][button];
            }
            public void SetOnColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _on_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetOnColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _on_colour[bank][button];
            }
            public void SetOffColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _off_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetOffColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _off_colour[bank][button];
            }
            public void SetFontFamily(int bank, int button, string font_family)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _fontFamily[bank][button] = font_family;
            }
            public string GetFontFamily(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return "";
                return _fontFamily[bank][button];
            }
            public void SetFontStyle(int bank, int button, FontStyle font_style)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _fontStyle[bank][button] = font_style;
            }
            public FontStyle GetFontStyle(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return FontStyle.Regular;
                return _fontStyle[bank][button];
            }
            public void SetFontSize(int bank, int button, float size)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _fontSize[bank][button] = size;
            }
            public float GetFontSize(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return 0;
                return _fontSize[bank][button];
            }
            public void SetFontColour(int bank, int button, System.Drawing.Color colour)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _font_colour[bank][button] = colour;
            }
            public System.Drawing.Color GetFontColour(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return System.Drawing.Color.Empty;
                return _font_colour[bank][button];
            }
            public void SetText(int bank, int button, string text)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _text[bank][button] = text;
            }
            public string GetText(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return "";
                return _text[bank][button];
            }
            public void SetEnabled(int bank, int button, bool enabled)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _enabled[bank][button] = enabled;
            }
            public bool GetEnabled(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return false;
                return _enabled[bank][button];
            }
            public void SetVisible(int bank, int button, bool enabled)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _visible[bank][button] = enabled;
            }
            public bool GetVisible(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return false;
                return _visible[bank][button];
            }
            public void SetIndicatorType(int bank, int button, IndicatorType type)
            {
                if (button < 0 || button >= _number_of_buttons) return;
                _indicator_type[bank][button] = type;
            }
            public IndicatorType GetIndicatorType(int bank, int button)
            {
                if (button < 0 || button >= _number_of_buttons) return IndicatorType.RING;
                return _indicator_type[bank][button];
            }
        }

        internal class clsVfoDisplay : clsMeterItem
        {
            public enum renderState
            {
                VFO = 0,
                BAND,
                MODE,
                FILTER,
                TUNE_STEP
            }
            public enum buttonState
            {   
                NONE = 0,
                VFO_SCREEN,
                BAND_SCREEN,
                FILTER_SCREEN,
                MODE_SCREEN,
                TUNE_STEP_SCREEN,
                VFO,
                BAND,
                VHF,
                HF,
                SWL,
                MODE,
                FILTER,
                SPLIT,
                TX,
                LOCK,
                VFO_SYNC,
                TUNE_STEP
            }
            public enum DSPModeForModeDisplay
            {
                LSB = 0,
                USB,
                DSB,
                CWL,
                CWU,
                FM,
                AM,
                SAM,
                DIGL,
                DIGU,
                SPEC,
                DRM
            }
            public enum VFODisplayMode
            {
                VFO_BOTH = 0,
                VFO_A = 1,
                VFO_B = 2
            }
            private renderState _render_state_vfoA;
            private renderState _render_state_vfoB;

            private VFODisplayMode _vfo_display_mode;

            private System.Drawing.Color _colour;
            private string _fontFamily;
            private FontStyle _fontStyle;
            private float _fontSize;

            private bool _showType;
            private System.Drawing.Color _typeColor;

            private System.Drawing.Color _frequencyColour;
            private System.Drawing.Color _frequencyColourSmall;
            private System.Drawing.Color _modeColour;
            private System.Drawing.Color _splitBackColour;
            private System.Drawing.Color _splitColour;
            private System.Drawing.Color _rxColour;
            private System.Drawing.Color _txColour;
            private System.Drawing.Color _filterColour;
            private System.Drawing.Color _bandColour;
            private System.Drawing.Color _digitHighlightColour;

            private double _adjust_step;
            private bool _mouse_over_vfoB;
            private clsMeter _owningmeter;
            private DateTime _render_state_vfoA_change_time;
            private DateTime _render_state_vfoB_change_time;
            private buttonState _render_button_vfoA;
            private buttonState _render_button_vfoB;
            private int _button_grid_index_vfoA;
            private int _button_grid_index_vfoB;

            private bool _band_text;
            private System.Drawing.Color _band_text_colour;

            private System.Drawing.Color _lock_colour;
            private System.Drawing.Color _sync_colour;

            private List<TuneStep> _tune_steps;

            private DateTime _mouse_down_time;
            private bool _long_press_released;

            public clsVfoDisplay(clsMeter owningmeter)
            {
                _tune_steps = new List<TuneStep>();
                //copy data from console to own local
                foreach(TuneStep ts in _console.TuneStepList)
                {
                    TuneStep nts = new TuneStep(ts.StepHz, ts.Name);
                    _tune_steps.Add(nts);
                }

                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontSize = 18f;
                _showType = true;
                _typeColor = System.Drawing.Color.Gray;
                _frequencyColour = System.Drawing.Color.Orange;
                _frequencyColourSmall = System.Drawing.Color.Orange;
                _modeColour = System.Drawing.Color.Gray;
                _splitBackColour = System.Drawing.Color.FromArgb(64, 64, 64);
                _splitColour = System.Drawing.Color.Orange;
                _rxColour = System.Drawing.Color.LimeGreen;
                _txColour = System.Drawing.Color.Red;
                _filterColour = System.Drawing.Color.Gray;
                _bandColour = System.Drawing.Color.White;
                _digitHighlightColour = System.Drawing.Color.FromArgb(128, 128, 128);
                _adjust_step = 0;
                _owningmeter = owningmeter;
                _mouse_over_vfoB = false;

                ItemType = MeterItemType.VFO_DISPLAY;
                _colour = System.Drawing.Color.White;
                StoreSettings = false;
                UpdateInterval = 50; // fixed

                _render_state_vfoA = renderState.VFO;
                _render_state_vfoB = renderState.VFO;
                _render_state_vfoA_change_time = DateTime.Now;
                _render_state_vfoB_change_time = DateTime.Now;
                _render_button_vfoA = buttonState.VFO_SCREEN;
                _render_button_vfoB = buttonState.VFO_SCREEN;
                _button_grid_index_vfoA = -1;
                _button_grid_index_vfoB = -1;

                _vfo_display_mode = VFODisplayMode.VFO_BOTH;

                _band_text = false;
                _band_text_colour = System.Drawing.Color.LimeGreen;

                _lock_colour = System.Drawing.Color.LimeGreen;
                _sync_colour = System.Drawing.Color.LimeGreen;

                _mouse_down_time = DateTime.MaxValue;
                _long_press_released = true;
            }
            public List<TuneStep> TuneSteps
            {
                get { return _tune_steps; }
            }
            public System.Drawing.Color LockColour
            {
                get { return _lock_colour; }
                set { _lock_colour = value; }
            }
            public System.Drawing.Color SyncColour
            {
                get { return _sync_colour; }
                set { _sync_colour = value; }
            }
            public bool ShowBandText
            {
                get { return _band_text; }
                set { _band_text = value; }
            }
            public System.Drawing.Color BandTextColour
            {
                get { return _band_text_colour; }
                set { _band_text_colour = value; }
            }
            public VFODisplayMode VFODispMode
            {
                get { return _vfo_display_mode; }
                set 
                { 
                    _vfo_display_mode = value;
                    switch (_vfo_display_mode)
                    {
                        case VFODisplayMode.VFO_BOTH:
                            VFOARenderState = renderState.VFO;
                            VFOBRenderState = renderState.VFO;
                            break;
                        case VFODisplayMode.VFO_A:
                            VFOBRenderState = renderState.VFO;
                            break;
                        case VFODisplayMode.VFO_B:
                            VFOARenderState = renderState.VFO;
                            break;
                    }
                }
            }
            private double getVfo()
            {
                double ret = -1;
                if (_owningmeter.RX == 1 && !_owningmeter.RX2Enabled)
                {
                    //vfoA
                    //vfoB
                    if (!_mouse_over_vfoB)
                        ret = _owningmeter.VfoA;
                    else
                        ret = _owningmeter.VfoB;
                }
                else if (_owningmeter.RX == 1 && _owningmeter.RX2Enabled)
                {
                    //vfoA
                    //no vfoB   if split / subrx then it becomes subvfo
                    if (!_mouse_over_vfoB)
                        ret = _owningmeter.VfoA;
                    else
                    {
                        if (_owningmeter.MultiRxEnabled || _owningmeter.Split)
                            ret = _owningmeter.VfoSub;
                    }
                }
                else if (_owningmeter.RX == 2 && _owningmeter.RX2Enabled)
                {
                    //no vfoA
                    //vfoB
                    if (_mouse_over_vfoB)
                        ret = _owningmeter.VfoB;
                }
                return ret;
            }
            private void setVfo(double value)
            {
                if (abortForLockedVFO()) return;
                if (_owningmeter.RX == 1 && !_owningmeter.RX2Enabled)
                {
                    //vfoA
                    //vfoB
                    if (!_mouse_over_vfoB)
                        _console.VFOAFreq = value;
                    else
                        _console.VFOBFreq = value;
                }
                else if (_owningmeter.RX == 1 && _owningmeter.RX2Enabled)
                {
                    //vfoA
                    //no vfoB   if split / subrx then it becomes subvfo
                    if (!_mouse_over_vfoB)
                        _console.VFOAFreq = value;
                    else
                    {
                        if (_owningmeter.MultiRxEnabled || _owningmeter.Split)
                            _console.VFOASubFreq = value;
                    }
                }
                else if (_owningmeter.RX == 2 && _owningmeter.RX2Enabled)
                {
                    //no vfoA
                    //vfoB
                    if (_mouse_over_vfoB)
                        _console.VFOBFreq = value;
                }
            }
            private bool abortForLockedVFO()
            {
                bool abort = false;
                if (_owningmeter.RX == 1 && !_owningmeter.RX2Enabled)
                {
                    //vfoA
                    //vfoB
                    if (!_mouse_over_vfoB)
                        return _owningmeter.VFOALock;
                    else
                        return _owningmeter.VFOBLock;
                }
                else if (_owningmeter.RX == 1 && _owningmeter.RX2Enabled)
                {
                    //vfoA
                    //no vfoB   if split / subrx then it becomes subvfo
                    if (!_mouse_over_vfoB)
                        return _owningmeter.VFOALock;
                    else
                    {
                        if (_owningmeter.MultiRxEnabled || _owningmeter.Split)
                            return _owningmeter.VFOALock;
                    }
                }
                else if (_owningmeter.RX == 2 && _owningmeter.RX2Enabled)
                {
                    //no vfoA
                    //vfoB
                    if (_mouse_over_vfoB)
                        return _owningmeter.VFOBLock;
                }
                return abort;
            }
            private void adjustVfo(double adjustment)
            {
                if (abortForLockedVFO()) return;
                if (_owningmeter.RX == 1 && !_owningmeter.RX2Enabled)
                {
                    //vfoA
                    //vfoB
                    if (!_mouse_over_vfoB)
                        _console.VFOAFreq += adjustment;
                    else
                        _console.VFOBFreq += adjustment;
                }
                else if (_owningmeter.RX == 1 && _owningmeter.RX2Enabled)
                {
                    //vfoA
                    //no vfoB   if split / subrx then it becomes subvfo
                    if (!_mouse_over_vfoB)
                        _console.VFOAFreq += adjustment;
                    else
                    {
                        if (_owningmeter.MultiRxEnabled || _owningmeter.Split)
                            _console.VFOASubFreq += adjustment;
                    }
                }
                else if (_owningmeter.RX == 2 && _owningmeter.RX2Enabled)
                {
                    //no vfoA
                    //vfoB
                    if (_mouse_over_vfoB)
                        _console.VFOBFreq += adjustment;
                }
            }
            public override void KeyDown(Keys keycode)
            {
                if (!MouseEntered) return;
                if ((int)_adjust_step == 0) return;

                if ((keycode >= Keys.D0 && keycode <= Keys.D9) ||
                    (keycode >= Keys.NumPad0 && keycode <= Keys.NumPad9))
                {
                    int digit = 0;
                    if (keycode >= Keys.D0 && keycode <= Keys.D9)
                        digit = keycode - Keys.D0;
                    else if (keycode >= Keys.NumPad0 && keycode <= Keys.NumPad9)
                        digit = keycode - Keys.NumPad0;

                    double freq = getVfo();
                    if(freq >= 0)
                    {
                        string freqs = freq.ToString("F6", CultureInfo.InvariantCulture);
                        int pos = freqs.Length - findOnePosition(_adjust_step * 1e-6) - 1;
                        string before = freqs.Substring(0, pos);
                        string after = freqs.Substring(pos + 1);
                        string newFreqs = $"{before}{digit}{after}";
                        bool ok = double.TryParse(newFreqs, out double newFreq);
                        if (ok)
                        {
                            _console.BeginInvoke(new MethodInvoker(() =>
                            {
                                setVfo(newFreq);
                            }));
                        }
                    }
                }
            }
            private int findOnePosition(double number)
            {
                string numberStr = number.ToString("F6", CultureInfo.InvariantCulture);
                for (int i = numberStr.Length - 1; i >= 0; i--)
                {
                    if (numberStr[i] == '1')
                    {
                        return numberStr.Length - 1 - i; // Return the 0-based index from the right
                    }
                }
                return -1; // Return -1 if '1' is not found
            }
            public override void MouseWheel(int number_of_moves)
            {
                if (!MouseEntered) return;
                if (_console == null) return;

                int sign = Math.Sign(number_of_moves);

                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    adjustVfo(sign * _adjust_step * 1e-6);
                }));                
            }
            public void PopBandStack()
            {
                if (!MouseEntered) return;
                if (_owningmeter.RX != 1) return; // only for rx1, vfoa right now

                if (_console != null)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {                        
                        _console.PopupBandstack(_owningmeter.RX, _owningmeter.BandVfoA, MeterManager.IsOnTop(_owningmeter.ID));
                    }));
                }

                _long_press_released = true;
            }
            public override void MouseDown(MouseEventArgs e)
            {
                if (!MouseEntered) return;

                _long_press_released = true;
                _mouse_down_time = DateTime.Now;
            }
            public bool MouseDownLong
            {
                get
                {
                    if (!MouseEntered) return false;

                    bool long_pressed = (DateTime.Now - _mouse_down_time).TotalMilliseconds >= 1000;
                    if (long_pressed) _long_press_released = false;
                    return long_pressed;
                }
            }
            public override void MouseUp(MouseEventArgs e)
            {
                if (!MouseEntered) return;
                if (_console == null) return;

                switch (_mouse_over_vfoB ? _render_button_vfoB : _render_button_vfoA) // in mouse up, as dont get mouseclicks when we mash the button
                {
                    case buttonState.VFO:
                        {
                            if (e.Button == MouseButtons.Left)
                            {
                                _console.BeginInvoke(new MethodInvoker(() =>
                                {
                                    adjustVfo(_adjust_step * 1e-6);
                                }));
                            }
                            else if (e.Button == MouseButtons.Right)
                            {
                                _console.BeginInvoke(new MethodInvoker(() =>
                                {
                                    adjustVfo(-_adjust_step * 1e-6);
                                }));
                            }
                        }
                        break;
                    case buttonState.BAND_SCREEN:
                        {
                            if (abortForLockedVFO()) break;
                            if (_mouse_over_vfoB)
                                VFOBRenderState = renderState.BAND;
                            else
                                VFOARenderState = renderState.BAND;
                        }
                        break;
                    case buttonState.VFO_SCREEN:
                        {
                            if (_mouse_over_vfoB)
                                VFOBRenderState = renderState.VFO;
                            else
                                VFOARenderState = renderState.VFO;
                        }
                        break;
                    case buttonState.FILTER_SCREEN:
                        {
                            if (_mouse_over_vfoB)
                                VFOBRenderState = renderState.FILTER;
                            else
                                VFOARenderState = renderState.FILTER;
                        }
                        break;
                    case buttonState.MODE_SCREEN:
                        {
                            if (abortForLockedVFO()) break;
                            if (_mouse_over_vfoB)
                                VFOBRenderState = renderState.MODE;
                            else
                                VFOARenderState = renderState.MODE;
                        }
                        break;
                    //////////
                    case buttonState.BAND:
                        {
                            if (abortForLockedVFO()) break;
                            if (_mouse_over_vfoB)
                            {
                                if (setBand(true))
                                    VFOBRenderState = renderState.VFO;
                                else
                                    _render_state_vfoB_change_time = DateTime.Now;
                            }
                            else
                            {
                                if (setBand(false))
                                    VFOARenderState = renderState.VFO;
                                else
                                    _render_state_vfoA_change_time = DateTime.Now;
                            }
                        }
                        break;
                    case buttonState.MODE:
                        {
                            if (_mouse_over_vfoB)
                            {
                                if (setMode(true))
                                    VFOBRenderState = renderState.VFO;
                                else
                                    _render_state_vfoB_change_time = DateTime.Now;
                            }
                            else
                            {
                                if (setMode(false))
                                    VFOARenderState = renderState.VFO;
                                else
                                    _render_state_vfoA_change_time = DateTime.Now;
                            }
                        }
                        break;
                    case buttonState.FILTER:
                        {
                            if (_mouse_over_vfoB)
                            {
                                if (setFilter(true))
                                    VFOBRenderState = renderState.VFO;
                                else
                                    _render_state_vfoB_change_time = DateTime.Now;
                            }
                            else
                            {
                                if (setFilter(false))
                                    VFOARenderState = renderState.VFO;
                                else
                                    _render_state_vfoA_change_time = DateTime.Now;
                            }
                        }
                        break;
                    case buttonState.SPLIT:
                        {
                            if (!_mouse_over_vfoB)
                            {
                                toggleSplit(e);
                            }
                        }
                        break;
                    case buttonState.TX:
                        {
                            if (_mouse_over_vfoB)
                            {
                                setTX(true); //vfoB
                            }
                            else
                            {
                                setTX(false); //vfoA
                            }
                        }
                        break;
                    case buttonState.LOCK:
                        {
                            if (_mouse_over_vfoB)
                            {
                                setLock(true); //vfoB
                            }
                            else
                            {
                                setLock(false); //vfoA
                            }
                        }
                        break;
                    case buttonState.VFO_SYNC:
                        {
                            // same for both vfoa/b
                            setVfoSync();
                        }
                        break;
                    case buttonState.TUNE_STEP:
                        {
                            if (_long_press_released)
                            {
                                if (_mouse_over_vfoB)
                                {
                                    if (setTuneStep(true))
                                        VFOBRenderState = renderState.VFO;
                                    else
                                        _render_state_vfoB_change_time = DateTime.Now;
                                }
                                else
                                {
                                    if (setTuneStep(false))
                                        VFOARenderState = renderState.VFO;
                                    else
                                        _render_state_vfoA_change_time = DateTime.Now;
                                }
                            }
                        }
                        break;
                }

                _long_press_released = true;
            }
            private bool setTuneStep(bool vfoB)
            {
                if (_console == null) return false;

                // index to TuneStep index map, as some have been ignored in the grid representation, see renderer
                int button = vfoB ? _button_grid_index_vfoB : _button_grid_index_vfoA;
                int tune_step_index = -1;

                switch (button)
                {
                    case 0:
                        tune_step_index = 0;
                        break;
                    case 1:
                        tune_step_index = 2;
                        break;
                    case 2:
                        tune_step_index = 3;
                        break;
                    case 3:
                        tune_step_index = 4;
                        break;
                    case 4:
                        tune_step_index = 5;
                        break;
                    case 5:
                        tune_step_index = 6;
                        break;
                    case 6:
                        tune_step_index = 7;
                        break;
                    case 7:
                        tune_step_index = 8;
                        break;
                    case 8:
                        tune_step_index = 11;
                        break;
                    case 9:
                        tune_step_index = 12;
                        break;
                    case 10:
                        tune_step_index = 14;
                        break;
                    case 11:
                        tune_step_index = 15;
                        break;
                    case 12:
                        tune_step_index = 17;
                        break;
                    case 13:
                        tune_step_index = 18;
                        break;
                    case 14:
                        tune_step_index = 20;
                        break;
                    case 15:
                        tune_step_index = 21;
                        break;
                }

                if (tune_step_index != -1)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.TuneStepIndex = tune_step_index;
                    }));
                    return true;
                }
                return false;
            }
            private void setLock(bool vfoB)
            {
                if (_console == null) return;
                if (vfoB)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.VFOBLock = !_console.VFOBLock;
                    }));
                }
                else
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.VFOALock = !_console.VFOALock;
                    }));
                }
            }
            private void setVfoSync()
            {
                if (_console == null) return;
                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.VFOSync = !_console.VFOSync;
                }));
            }
            private void setTX(bool vfoB)
            {
                if (_console == null) return;
                if (vfoB)
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.VFOBTX = true;
                    }));
                }
                else
                {
                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.VFOATX = true;
                    }));
                }
            }
            private void toggleSplit(MouseEventArgs e)
            {
                if (_console == null) return;
                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    if (e.Button == MouseButtons.Left && Keyboard.IsKeyDown(Keys.LShiftKey))
                    {
                        if (!_console.IsSetupFormNull && !_console.RX2Enabled) _console.SetupForm.QuickSplitEnabled = !_console.SetupForm.QuickSplitEnabled;
                    }
                    else
                    {
                        _console.VFOSplit = !_console.VFOSplit;
                    }
                }));
            }
            private bool setFilter(bool vfoB)
            {
                if (vfoB)
                {
                    int f = _button_grid_index_vfoB;
                    if (f < 0) return true;
                    Filter fltr = (Filter)((int)Filter.F1 + f);

                    if (_owningmeter.RX2Enabled)
                    {
                        // special for rx2 as only F1-F7 + VAR1 + VAR2, so, 0,1,2,3,4,5,6,7,8
                        if (f > 8) return true;
                        if(f > 6)
                        {
                            f += 3;
                            fltr = (Filter)((int)Filter.F1 + f);
                        }
                        _console.BeginInvoke(new MethodInvoker(() =>
                        {
                            _console.RX2Filter = fltr;
                        }));
                    }
                    else
                    {
                        _console.BeginInvoke(new MethodInvoker(() =>
                        {
                            _console.RX1Filter = fltr;
                        }));
                    }
                }
                else
                {
                    int f = _button_grid_index_vfoA;
                    if (f < 0) return true;
                    Filter fltr = (Filter)((int)Filter.F1 + f);

                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.RX1Filter = fltr;
                    }));
                }
                return true;
            }
            private bool setMode(bool vfoB)
            {
                if (abortForLockedVFO()) return false;
                if (vfoB)
                {
                    int m = _button_grid_index_vfoB;
                    if (m < 0) return true;
                    DSPModeForModeDisplay dm = (DSPModeForModeDisplay)((int)DSPModeForModeDisplay.LSB + m);
                    Enum.TryParse<DSPMode>(dm.ToString(), out DSPMode dspMode);

                    if (_owningmeter.RX2Enabled)
                    {
                        _console.BeginInvoke(new MethodInvoker(() =>
                        {
                            _console.RX2DSPMode = dspMode;
                        }));
                    }
                    else
                    {
                        _console.BeginInvoke(new MethodInvoker(() =>
                        {
                            _console.RX1DSPMode = dspMode;
                        }));
                    }
                }
                else
                {
                    int m = _button_grid_index_vfoA;
                    if (m < 0) return true;
                    DSPModeForModeDisplay dm = (DSPModeForModeDisplay)((int)DSPModeForModeDisplay.LSB + m);
                    Enum.TryParse<DSPMode>(dm.ToString(), out DSPMode dspMode);

                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.RX1DSPMode = dspMode;
                    }));
                }
                return true;
            }
            private bool setBand(bool vfoB)
            {
                if (abortForLockedVFO()) return false;
                if (vfoB)
                {
                    int b = _button_grid_index_vfoB;
                    if (b < 0) return true;
                    Band band;
                    if (_console.BandGENSelected)
                    {
                        if(b > (int)Band.B11M - (int)Band.B120M)
                        {
                            if (b == 13)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, true, false); // HF
                                return false;
                            }
                            return true;
                        }
                        band = (Band)((int)Band.B120M + b);
                    }
                    else if (_console.BandHFSelected)
                    {
                        if (b > (int)Band.B6M - (int)Band.B160M)
                        {
                            if (b == 11)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, false, true); // VHF
                                return false;
                            }
                            if (b == 13)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, true, false, false); // GEN(SWL)
                                return false;
                            }
                            if (b == 12) // WWV
                                band = Band.WWV;
                            else
                                return true;
                        }
                        band = (Band)((int)Band.B160M + b);
                    }
                    else if (_console.BandVHFSelected)
                    {
                        if (b > (int)Band.VHF13 - (int)Band.VHF0)
                        {
                            if (b == 14)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, true, false); // HF
                                return false;
                            }
                            return true;
                        }
                        band = (Band)((int)Band.VHF0 + b);
                    }
                    else
                        return true;

                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.SetupRX2Band(band, !_owningmeter.RX2Enabled); // we tell setuprx2band that we only want to change freq for vfob if only rx1
                    }));
                }
                else
                {
                    int b = _button_grid_index_vfoA;
                    if (b < 0) return true;
                    Band band;
                    if (_console.BandGENSelected)
                    {
                        if (b > (int)Band.B11M - (int)Band.B120M)
                        {
                            if (b == 13)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, true, false); // HF
                                return false;
                            }
                            return true;
                        }
                        band = (Band)((int)Band.B120M + b);
                    }
                    else if (_console.BandHFSelected)
                    {
                        if (b > (int)Band.B6M - (int)Band.B160M)
                        {
                            if (b == 11)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, false, true); // VHF
                                return false;
                            }
                            if (b == 13)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, true, false, false); // GEN(SWL)
                                return false;
                            }
                            if (b == 12) // WWV
                                band = Band.WWV;
                            else
                                return true;
                        }
                        band = (Band)((int)Band.B160M + b);
                    }
                    else if (_console.BandVHFSelected)
                    {
                        if (b > (int)Band.VHF13 - (int)Band.VHF0)
                        {
                            if (b == 14)
                            {
                                _owningmeter.SetBandPanel(_console, _owningmeter.RX, false, true, false); // HF
                                return false;
                            }
                            return true;
                        }

                        band = (Band)((int)Band.VHF0 + b);
                    }
                    else
                        return true;

                    _console.BeginInvoke(new MethodInvoker(() =>
                    {
                        _console.BandPreChangeHandlers?.Invoke(1, band);
                    }));
                }
                return true;
            }
            public int ButtonGridIndexVFOa
            {
                get { return _button_grid_index_vfoA; }
                set { _button_grid_index_vfoA = value; }
            }
            public int ButtonGridIndexVFOb
            {
                get { return _button_grid_index_vfoB; }
                set { _button_grid_index_vfoB = value; }
            }
            public double AdjustStep
            {
                get { return _adjust_step; }
                set { _adjust_step = value; }
            }
            public bool MouseOverVfoB
            {
                get { return _mouse_over_vfoB; }
                set { _mouse_over_vfoB = value; }
            }
            public renderState VFOARenderState
            {
                get 
                {
                    if (_render_state_vfoA != renderState.VFO && (DateTime.Now - _render_state_vfoA_change_time).Seconds > 5) VFOARenderState = renderState.VFO;
                    return _render_state_vfoA; 
                }
                set 
                {
                    if (_render_state_vfoA != value) _render_state_vfoA_change_time = DateTime.Now;
                    _render_state_vfoA = value; 
                }
            }
            public renderState VFOBRenderState
            {
                get
                {
                    if (_render_state_vfoB != renderState.VFO && (DateTime.Now - _render_state_vfoB_change_time).Seconds > 5) VFOBRenderState = renderState.VFO;
                    return _render_state_vfoB; 
                }
                set 
                {
                    if (_render_state_vfoB != value) _render_state_vfoB_change_time = DateTime.Now;
                    _render_state_vfoB = value; 
                }
            }
            public buttonState VFOAButtonState
            {
                get { return _render_button_vfoA; }
                set { _render_button_vfoA = value; }
            }
            public buttonState VFOBButtonState
            {
                get { return _render_button_vfoB; }
                set { _render_button_vfoB = value; }
            }
            public System.Drawing.Color FrequencyColour
            {
                get { return _frequencyColour; }
                set { _frequencyColour = value; }
            }
            public System.Drawing.Color FrequencyColourSmall
            {
                get { return _frequencyColourSmall; }
                set { _frequencyColourSmall = value; }
            }
            public System.Drawing.Color ModeColour
            {
                get { return _modeColour; }
                set { _modeColour = value; }
            }
            public System.Drawing.Color SplitBackColour
            {
                get { return _splitBackColour; }
                set { _splitBackColour = value; }
            }
            public System.Drawing.Color SplitColour
            {
                get { return _splitColour; }
                set { _splitColour = value; }
            }
            public System.Drawing.Color RxColour
            {
                get { return _rxColour; }
                set { _rxColour = value; }
            }
            public System.Drawing.Color TxColour
            {
                get { return _txColour; }
                set { _txColour = value; }
            }
            public System.Drawing.Color FilterColour
            {
                get { return _filterColour; }
                set { _filterColour = value; }
            }
            public System.Drawing.Color BandColour
            {
                get { return _bandColour; }
                set { _bandColour = value; }
            }
            public System.Drawing.Color DigitHighlightColour
            {
                get { return _digitHighlightColour; }
                set { _digitHighlightColour = value; }
            }
            public bool ShowType
            {
                get { return _showType; }
                set { _showType = value; }
            }
            public System.Drawing.Color TypeColour
            {
                get { return _typeColor; }
                set { _typeColor = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle Style
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set { _colour = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }

            //public override string ToString()
            //{
            //    string sRet;

            //    sRet = ID + "|" +
            //        ParentID + "|" +
            //        ItemType.ToString() + "|" +
            //        TopLeft.X.ToString("f4") + "|" +
            //        TopLeft.Y.ToString("f4") + "|" +
            //        Size.Width.ToString("f4") + "|" +
            //        Size.Height.ToString("f4") + "|" +
            //        //
            //        _colour.ToString();

            //    return sRet;
            //}
        }
        internal class clsClock : clsMeterItem
        {
            private System.Drawing.Color _colour;
            private string _fontFamily;
            private FontStyle _fontStyle;
            private float _fontSize;
            private System.Drawing.Color _timeColour;
            private System.Drawing.Color _dateColour;
            private System.Drawing.Color _typeTitleColour;
            private bool _showType;
            private bool _24HourClock;

            public clsClock()
            {
                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontSize = 18f;
                _showType = true;
                _timeColour = System.Drawing.Color.Orange;
                _dateColour = System.Drawing.Color.Gray;
                _typeTitleColour = System.Drawing.Color.Gray;
                _24HourClock = true;

                ItemType = MeterItemType.CLOCK;
                _colour = System.Drawing.Color.White;
                StoreSettings = false;
                UpdateInterval = 250; // fixed
            }
            public bool Show24HourCLock
            {
                get { return _24HourClock; }
                set { _24HourClock = value; }
            }
            public bool ShowType
            {
                get { return _showType; }
                set { _showType = value; }
            }
            public System.Drawing.Color TimeColour
            {
                get { return _timeColour; }
                set { _timeColour = value; }
            }
            public System.Drawing.Color DateColour
            {
                get { return _dateColour; }
                set { _dateColour = value; }
            }
            public System.Drawing.Color TypeTitleColour
            {
                get { return _typeTitleColour; }
                set { _typeTitleColour = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle Style
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set { _colour = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }
            //public override string ToString()
            //{
            //    string sRet;

            //    sRet = ID + "|" +
            //        ParentID + "|" +
            //        ItemType.ToString() + "|" +
            //        TopLeft.X.ToString("f4") + "|" +
            //        TopLeft.Y.ToString("f4") + "|" +
            //        Size.Width.ToString("f4") + "|" +
            //        Size.Height.ToString("f4") + "|" +
            //        //
            //        _colour.ToString();

            //    return sRet;
            //}
        }
        internal class clsWebImage : clsMeterItem
        {
            private PointF _clipTopLeft;
            private SizeF _clipSize;
            private bool _clipped;
            private bool _darkMode;
            private Guid _image_fetcher_guid;
            private Guid _bitmap_guid;
            private string _url;
            private int _secs_interval;
            private bool _bypass_cache;
            private System.Drawing.Bitmap _bitmap;
            private clsMeter _owningMeter;
            private readonly object _bitmap_lock = new object();
            private clsItemGroup _ig;
            private float _width_scale;
            private float _size;
            private ImageFetcher.State _state;

            public clsWebImage(clsMeter owningMeter, clsItemGroup ig)
            {
                _owningMeter = owningMeter;

                ItemType = MeterItemType.WEB_IMAGE;

                _clipTopLeft = new PointF(0f, 0f);
                _clipSize = new SizeF(1f, 1f);
                _clipped = false;
                _darkMode = false;
                StoreSettings = false;
                _image_fetcher_guid = Guid.Empty;
                _bitmap_guid = Guid.Empty;
                _bitmap = null;
                _url = "";
                _secs_interval = 120;
                _bypass_cache = false;
                _ig = ig;
                _width_scale = 1f;
                _size = 0.1f;
                UpdateInterval = 100;
                _state = ImageFetcher.State.IDLE;
            }
            public override void Removing()
            {
                if(_image_fetcher_guid != Guid.Empty)
                {
                    MeterManager.ImgFetch.ImagesObtained -= OnImage;
                    MeterManager.ImgFetch.StateChanged -= OnState;
                    MeterManager.ImgFetch.StopFetching(_image_fetcher_guid);
                }

                string key = "";
                lock (_bitmap_lock)
                {
                    if (_bitmap != null)
                    {                        
                        _bitmap.Dispose();
                        key = "webimg_" + _image_fetcher_guid.ToString();
                    }

                    if(!string.IsNullOrEmpty(key)) MeterManager.RemoveStreamData(key);
                }
            }

            public System.Drawing.Bitmap Bitmap
            {
                get 
                {
                    lock (_bitmap_lock)
                    {
                        return _bitmap;
                    }
                }
            }
            public int SecondsInterval
            {
                get { return _secs_interval; }
                set 
                { 
                    _secs_interval = value; 

                    // change the related image fetcher if one exists
                    if(_image_fetcher_guid != Guid.Empty)
                    {
                        MeterManager.ImgFetch.UpdateInterval(_image_fetcher_guid, _secs_interval);
                    }
                }
            }public bool BypassCache
            {
                get { return _bypass_cache; }
                set
                {
                    _bypass_cache = value;

                    if (_image_fetcher_guid != Guid.Empty)
                    {
                        MeterManager.ImgFetch.UpdateBypassCache(_image_fetcher_guid, _bypass_cache);
                    }
                }
            }
            public Guid BitmapGuid
            {
                get                 
                {
                    lock (_bitmap_lock)
                    {
                        return _bitmap_guid;
                    }
                }
            }
            public Guid FetcherGuid
            {
                get
                {
                    lock (_bitmap_lock)
                    {
                        return _image_fetcher_guid;
                    }
                }
            }
            public string URL
            {
                get { return _url; }
                set
                {
                    if (value == _url) return;
                    if(value != _url && _image_fetcher_guid != Guid.Empty)
                    {
                        MeterManager.ImgFetch.ImagesObtained -= OnImage;
                        MeterManager.ImgFetch.StateChanged -= OnState;
                        MeterManager.ImgFetch.StopFetching(_image_fetcher_guid);
                        _image_fetcher_guid = Guid.Empty;
                    }
                    //
                    bool isFile = false;
                    string sPath = "";
                    if (!Common.IsValidUri(value))
                    {
                        // check if it is a file
                        if(value.ToLower().IndexOf("file://") >= 0)
                        {
                            try
                            {
                                Uri file = new Uri(value);
                                // does this path exist?
                                if (!File.Exists(file.LocalPath)) return;
                                isFile = true;
                                sPath = file.LocalPath;
                            }
                            catch 
                            {
                                return;
                            }
                        }
                        else
                            return;
                    }
                    //
                    _url = value;
                    string url = isFile ? sPath : _url;
                    try
                    {
                        MeterManager.ImgFetch.StateChanged += OnState;
                        MeterManager.ImgFetch.ImagesObtained += OnImage;
                        _image_fetcher_guid = MeterManager.ImgFetch.RegisterURL(url, _secs_interval, 1, isFile, _bypass_cache);
                    }
                    catch
                    {
                        MeterManager.ImgFetch.ImagesObtained -= OnImage;
                        MeterManager.ImgFetch.StateChanged -= OnState;
                        _image_fetcher_guid = Guid.Empty;
                    }
                }
            }
            private void OnState(object sender, ImageFetcher.StateEventArgs e)
            {
                if (e.Guid != _image_fetcher_guid) return;

                _state = e.WebImageState;
                //Debug.Print(_state.ToString());
                if (!_console.IsSetupFormNull)
                {
                    _console.SetupForm.SetWebImageState(ParentID, _state);
                }
            }
            public ImageFetcher.State WebImageState
            {
                get { return _state; }
            }
            private void OnImage(object sender, Guid guid)
            {
                if (guid != _image_fetcher_guid) return;

                lock (_bitmap_lock)
                {
                    List<System.Drawing.Image> imgs = MeterManager.ImgFetch.LatestImages(guid);
                    if (imgs.Count == 1)
                    {
                        Debug.Print(">>>>>>> ONE IMAGE <<<<<<<<");

                        if (_bitmap != null)
                            _bitmap.Dispose();

                        try
                        {
                            _bitmap_guid = Guid.NewGuid();
                            _bitmap = new System.Drawing.Bitmap(imgs[0]);
                        }
                        catch
                        {
                            _bitmap_guid = Guid.Empty;
                            _bitmap = null;
                        }

                        if (_bitmap != null)
                        {
                            _size = (_bitmap.Height / (float)_bitmap.Width) * _width_scale;
                        }
                    }
                }

                if (_size >= 0 && this.Size.Height != _size)
                {
                    this.Size = new SizeF(/*1f*/ _width_scale, _size);

                    if (_ig != null)
                    {
                        float fPadY = 0.041f;
                        float fHeight = 0.05f;
                        _ig.Size = new SizeF(_ig.Size.Width, _size + (fPadY - (fHeight * 0.75f)));
                        _owningMeter.Rebuild();
                    }
                }
            }
            public float ScaleSize
            {
                get { return _size; }
            }
            public float WidthScale
            {
                get { return _width_scale; }
                set 
                { 
                    _width_scale = value;
                    lock (_bitmap_lock)
                    {
                        if (_bitmap != null)
                        {
                            _size = (_bitmap.Height / (float)_bitmap.Width) * _width_scale;
                        }
                    }
                }
            }
            public PointF ClipTopLeft
            {
                get { return _clipTopLeft; }
                set { _clipTopLeft = value; }
            }
            public SizeF ClipSize
            {
                get { return _clipSize; }
                set { _clipSize = value; }
            }
            public bool Clipped
            {
                get { return _clipped; }
                set { _clipped = value; }
            }
            public bool DarkMode
            {
                get { return _darkMode; }
                set { _darkMode = value; }
            }
        }
        internal class clsImage : clsMeterItem
        {
            private string _name;
            private PointF _clipTopLeft;
            private SizeF _clipSize;
            private bool _clipped;
            private bool _clipped_ellipse;
            private bool _darkMode;
            private PointF _clipEllipseCentre;
            private SizeF _clipEllipseRadius;
            public clsImage()
            {
                ItemType = MeterItemType.IMAGE;

                _name = "";
                _clipTopLeft = new PointF(0f, 0f);
                _clipSize = new SizeF(1f, 1f);
                _clipped = false;
                _clipped_ellipse = false;
                _darkMode = false;
                StoreSettings = false;
            }
            public string ImageName
            {
                get { return _name; }
                set
                {
                    _name = value;
                }
            }
            public PointF ClipTopLeft
            {
                get { return _clipTopLeft; }
                set { _clipTopLeft = value; }
            }
            public SizeF ClipSize
            {
                get { return _clipSize; }
                set { _clipSize = value; }
            }
            public bool Clipped
            {
                get { return _clipped; }
                set { _clipped = value; }
            }
            public PointF ClipEllipseCentre
            {
                get { return _clipEllipseCentre; }
                set { _clipEllipseCentre = value; }
            }
            public SizeF ClipEllipseRadius
            {
                get { return _clipEllipseRadius; }
                set { _clipEllipseRadius = value; }
            }
            public bool ClippedEllipse
            {
                get { return _clipped_ellipse; }
                set { _clipped_ellipse = value; }
            }
            public bool DarkMode
            {
                get { return _darkMode; }
                set { _darkMode = value; }
            }
            //public override string ToString()
            //{
            //    string sRet;

            //    sRet = ID + "|" +
            //        ParentID + "|" +
            //        ItemType.ToString() + "|" +
            //        TopLeft.X.ToString("f4") + "|" +
            //        TopLeft.Y.ToString("f4") + "|" +
            //        Size.Width.ToString("f4") + "|" +
            //        Size.Height.ToString("f4") + "|";

            //    return sRet;
            //}
        }
        internal class clsScaleItem : clsMeterItem
        {
            private System.Drawing.Color _lowColour;
            private System.Drawing.Color _highColour;
            private string _fontFamily;
            private FontStyle _fontStyle;
            private System.Drawing.Color _fontColorLow;
            private System.Drawing.Color _fontColorHigh;
            private System.Drawing.Color _fontColourMeterType;
            private float _fontSize;
            private bool _showType;
            private bool _showMarkers;

            public clsScaleItem()
            {
                _lowColour = System.Drawing.Color.White;
                _highColour = System.Drawing.Color.Red;
                _fontColourMeterType = System.Drawing.Color.DarkGray;

                _showType = false;
                _showMarkers = true;

                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontColorLow = System.Drawing.Color.White;
                _fontColorHigh = System.Drawing.Color.Red;
                _fontColourMeterType = System.Drawing.Color.DarkGray;
                _fontSize = 20f;               

                ItemType = MeterItemType.H_SCALE;
                ReadingSource = Reading.NONE;
                UpdateInterval = 100;
                AttackRatio = 0.8f;
                DecayRatio = 0.2f;
                StoreSettings = false;
            }           
            public System.Drawing.Color LowColour
            {
                get { return _lowColour; }
                set { _lowColour = value; }
            }
            public System.Drawing.Color HighColour
            {
                get { return _highColour; }
                set { _highColour = value; }
            }
            public System.Drawing.Color FontColourType
            {
                get { return _fontColourMeterType; }
                set { _fontColourMeterType = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle FntStyle
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color FontColourLow
            {
                get { return _fontColorLow; }
                set { _fontColorLow = value; }
            }
            public System.Drawing.Color FontColourHigh
            {
                get { return _fontColorHigh; }
                set { _fontColorHigh = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }
            public bool ShowType
            {
                get { return _showType; }
                set { _showType = value; }
            }  
            public bool ShowMarkers
            {
                get { return _showMarkers; }
                set { _showMarkers = value; }
            }
            public override string ToString()
            {
                string sRet;

                sRet = ID + "|" +
                    ParentID + "|" +
                    ItemType.ToString() + "|" +
                    TopLeft.X.ToString("f4") + "|" +
                    TopLeft.Y.ToString("f4") + "|" +
                    Size.Width.ToString("f4") + "|" +
                    Size.Height.ToString("f4");
                    //


                return sRet;
            }            
        }
        internal class clsNeedleScalePwrItem : clsMeterItem
        {
            private System.Drawing.Color _lowColour;
            private System.Drawing.Color _highColour;
            private string _fontFamily;
            private FontStyle _fontStyle;
            private System.Drawing.Color _fontColorLow;
            private System.Drawing.Color _fontColorHigh;
            private System.Drawing.Color _fontColourType;
            private float _fontSize;
            private bool _showType;
            private bool _showMarkers;
            private int _marks;
            private bool _darkMode;

            private clsNeedleItem.NeedleDirection _needleDirection;
            private float _lengthFactor;
            private PointF _radiusRatio;
            private clsNeedleItem.NeedlePlacement _placement;
            private PointF _needleOffset;

            public clsNeedleScalePwrItem()
            {
                _lowColour = System.Drawing.Color.White;
                _highColour = System.Drawing.Color.Red;
                _fontColourType = System.Drawing.Color.DarkGray;

                _showType = false;
                _showMarkers = true;

                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontColorLow = System.Drawing.Color.White;
                _fontColorHigh = System.Drawing.Color.Red;
                _fontColourType = System.Drawing.Color.DarkGray;
                _fontSize = 20f;

                ItemType = MeterItemType.NEEDLE_SCALE_PWR;
                ReadingSource = Reading.NONE;
                UpdateInterval = 100;
                AttackRatio = 0.8f;
                DecayRatio = 0.2f;
                StoreSettings = false;

                _radiusRatio.X = 1f;
                _radiusRatio.Y = 1f;
                _lengthFactor = 1f;
                _needleDirection = clsNeedleItem.NeedleDirection.Clockwise;
                _placement = clsNeedleItem.NeedlePlacement.Bottom;
                _needleOffset.X = 0f;
                _needleOffset.Y = 0.5f;
                _darkMode = false;
            }            
            public System.Drawing.Color LowColour
            {
                get { return _lowColour; }
                set { _lowColour = value; }
            }
            public System.Drawing.Color HighColour
            {
                get { return _highColour; }
                set { _highColour = value; }
            }
            public System.Drawing.Color FontColourType
            {
                get { return _fontColourType; }
                set { _fontColourType = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle FntStyle
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color FontColourLow
            {
                get { return _fontColorLow; }
                set { _fontColorLow = value; }
            }
            public System.Drawing.Color FontColourHigh
            {
                get { return _fontColorHigh; }
                set { _fontColorHigh = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }
            public bool ShowType
            {
                get { return _showType; }
                set { _showType = value; }
            }
            public bool ShowMarkers
            {
                get { return _showMarkers; }
                set { _showMarkers = value; }
            }
            public override string ToString()
            {
                string sRet;

                sRet = ID + "|" +
                    ParentID + "|" +
                    ItemType.ToString() + "|" +
                    TopLeft.X.ToString("f4") + "|" +
                    TopLeft.Y.ToString("f4") + "|" +
                    Size.Width.ToString("f4") + "|" +
                    Size.Height.ToString("f4");
                //

                return sRet;
            }
            public clsNeedleItem.NeedlePlacement Placement
            {
                get { return _placement; }
                set { _placement = value; }
            }
            public clsNeedleItem.NeedleDirection Direction
            {
                get { return _needleDirection; }
                set { _needleDirection = value; }
            }
            public PointF NeedleOffset
            {
                get { return _needleOffset; }
                set { _needleOffset = value; }
            }
            public PointF RadiusRatio
            {
                get { return _radiusRatio; }
                set { _radiusRatio = value; }
            }
            public float LengthFactor
            {
                get { return _lengthFactor; }
                set { _lengthFactor = value; }
            }
            public int Marks
            {
                get { return _marks; }
                set { _marks = value; }
            }
            public bool DarkMode
            {
                get { return _darkMode; }
                set { _darkMode = value; }
            }
        }
        internal class clsRotatorItem : clsMeterItem
        {
            public enum RotatorMode
            {
                AZ = 0,
                ELE = 1,
                BOTH = 2
            }
            private string _fontFamily;
            private FontStyle _fontStyle;
            private float _fontSize;
            private bool _showValue;
            private bool _darkMode;
            private RotatorMode _rotator_mode;

            private Guid _data_out_mmio_guid;

            private bool _alow_control;
            private string _control_string_AZ;
            private string _control_string_ELE;
            private string _control_string_STOP;

            private readonly object _historyLock = new object();

            private System.Drawing.Color _big_blob_colour;
            private System.Drawing.Color _small_blob_colour;
            private System.Drawing.Color _outer_text_colour;
            private System.Drawing.Color _arrow_colour;
            private System.Drawing.Color _beam_width_colour;
            private System.Drawing.Color _background_colour;
            private System.Drawing.Color _control_colour;

            //private bool _show_elevation;
            private bool _show_cardinals;
            private bool _show_beam_width;
            private float _beam_width;
            private float _beam_width_alpha;
            private float _padding;

            public clsRotatorItem()
            {
                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontSize = 18f;
                _showValue = true;
                //_show_elevation = false;
                _show_cardinals = false;
                _show_beam_width = false;
                _beam_width = 30f;
                _beam_width_alpha = 0.6f;
                _darkMode = false;
                _rotator_mode = RotatorMode.BOTH;
                _padding = 0.5f;

                _data_out_mmio_guid = Guid.Empty;

                _big_blob_colour = System.Drawing.Color.Red;
                _small_blob_colour = System.Drawing.Color.White;
                _outer_text_colour = System.Drawing.Color.FromArgb(255, 128, 128, 128);
                _arrow_colour = System.Drawing.Color.White;
                _beam_width_colour = System.Drawing.Color.FromArgb(255, 64, 64, 64);
                _background_colour = System.Drawing.Color.FromArgb(32, 32, 32);
                _control_colour = System.Drawing.Color.LimeGreen;

                _alow_control = false;
                _control_string_AZ = "<PST><AZIMUTH>%AZ%</AZIMUTH></PST>";
                _control_string_ELE = "<PST><ELEVATION>%ELE%</ELEVATION></PST>";
                _control_string_STOP = "<PST><STOP>1</STOP></PST>";

                ItemType = MeterItemType.ROTATOR;
                ReadingSource = Reading.AZ;
                UpdateInterval = 1000;
                StoreSettings = false;
            }
            public RotatorMode ViewMode
            {
                get { return _rotator_mode; }
                set { _rotator_mode = value; }
            }
            public Guid DataOutMMIOGuid
            {
                get { return _data_out_mmio_guid; }
                set { _data_out_mmio_guid = value; }
            }
            public bool AllowControl
            {
                get { return _alow_control; }
                set { _alow_control = value; }
            }
            public System.Drawing.Color ControlColour
            {
                get { return _control_colour; }
                set { _control_colour = value; }
            }
            public string AZControlString
            {
                get { return _control_string_AZ; }
                set { _control_string_AZ = value; }
            }
            public string STOPControlString
            {
                get { return _control_string_STOP; }
                set { _control_string_STOP = value; }
            }
            public string ELEControlString
            {
                get { return _control_string_ELE; }
                set { _control_string_ELE = value; }
            }
            public void SendRotatorMessage(bool dragging_rotator_ele, float dragging_rotator_degrees, bool stop)
            {
                if (DataOutMMIOGuid == Guid.Empty) return;
                if (!_alow_control) return;

                if (MultiMeterIO.Data.ContainsKey(DataOutMMIOGuid))
                {
                    MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[DataOutMMIOGuid];
                    if (mmio == null) return;

                    string data;
                    if (stop)
                    {
                        data = _control_string_STOP;
                    }
                    else
                    {
                        if (dragging_rotator_ele)
                        {
                            data = _control_string_ELE.Replace("%ELE%", ((int)dragging_rotator_degrees).ToString("00"));
                        }
                        else
                        {
                            data = _control_string_AZ.Replace("%AZ%", ((int)dragging_rotator_degrees).ToString("000"));
                        }
                    }
                    data = data.Replace(@"\n", "\n"); // use @ so that it is a literal verbatim string
                    data = data.Replace(@"\r", "\r");
                    data = data.Replace(@"\0", "\0");
                    mmio.EnqueueOutbound(data);
                }
            }
            public string ImageName
            {
                get
                {
                    switch(_rotator_mode)
                    {
                        case RotatorMode.AZ:
                            return "rotator_az-bg";
                        case RotatorMode.ELE:
                            return "rotator_ele-bg";
                        case RotatorMode.BOTH:
                            return "rotator_both-bg";
                    }
                    return "";
                }
            }
            public string MapName
            {
                get
                {
                    switch (_rotator_mode)
                    {
                        case RotatorMode.AZ:
                        case RotatorMode.BOTH:
                            return "rotator_map-bg";
                    }
                    return "";
                }
            }
            public float Padding
            {
                get { return _padding; }
                set {
                    if (value < 0.001f) value = 0.001f;
                    _padding = value; 
                }
            }
            public bool DarkMode
            {
                get { return _darkMode; }
                set { _darkMode = value; }
            }
            public float BeamWidth
            {
                get { return _beam_width; }
                set { _beam_width = value; }
            }
            public float BeamWidthAlpha
            {
                get { return _beam_width_alpha; }
                set { _beam_width_alpha = value; }
            }
            //public bool ShowElevation
            //{
            //    get { return _show_elevation; }
            //    set { _show_elevation = value; }
            //}
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // get latest reading
                float reading;
                bool use;
                
                if (MMIOGuid == Guid.Empty)
                {
                    reading = MeterManager.getReading(rx, ReadingSource);
                    use = true;
                }
                else
                {
                    reading = 0;
                    if (MultiMeterIO.Data.ContainsKey(MMIOGuid))
                    {
                        MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[MMIOGuid];
                        if (mmio == null) return;

                        object val = mmio.GetVariable(MMIOVariable);
                        if (val is int)
                        {
                            int intVal = (int)val;
                            reading = (float)intVal;
                        }
                        else if (val is float)
                        {
                            reading = (float)val;
                        }
                        else if (val is double)
                        {
                            double doubleVal = (double)val;
                            reading = (float)doubleVal;
                        }
                    }
                    use = false;
                }

                lock (_historyLock)
                {
                    if (ReadingSource == Reading.ELE)
                    {
                        if (reading < 0) reading = 0;
                        if (reading > 90) reading = 90;
                    }

                    float normalizedValue = Value % 360;
                    if (normalizedValue < 0) normalizedValue += 360;

                    float normalizedReading = reading % 360;
                    if (normalizedReading < 0) normalizedReading += 360;

                    float difference = normalizedReading - normalizedValue;
                    if (difference > 180)
                        difference -= 360;
                    else if (difference < -180)
                        difference += 360;


                    float adjustmentSpeed = 0.2f * Math.Abs(difference);

                    if (Math.Abs(difference) < adjustmentSpeed)
                        Value = reading;
                    else
                        Value += Math.Sign(difference) * adjustmentSpeed;

                    Value = Value % 360;
                    if (Value < 0) Value += 360;
                }

                if (use)
                {
                    // this reading has been used
                    if (!readingsUsed.Contains(ReadingSource))
                        readingsUsed.Add(ReadingSource);
                }
            }
            public bool ShowValue
            {
                get { return _showValue; }
                set { _showValue = value; }
            }
            public bool ShowCardinals
            {
                get { return _show_cardinals; }
                set { _show_cardinals = value; }
            }
            public bool ShowBeamWidth
            {
                get { return _show_beam_width; }
                set { _show_beam_width = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle Style
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color BigBlobColour
            {
                get { return _big_blob_colour; }
                set { _big_blob_colour = value; }
            }
            public System.Drawing.Color SmallBlobColour
            {
                get { return _small_blob_colour; }
                set { _small_blob_colour = value; }
            }
            public System.Drawing.Color OuterTextColour
            {
                get { return _outer_text_colour; }
                set { _outer_text_colour = value; }
            }
            public System.Drawing.Color ArrowColour
            {
                get { return _arrow_colour; }
                set { _arrow_colour = value; }
            }
            public System.Drawing.Color BeamWidthColour
            {
                get { return _beam_width_colour; }
                set { _beam_width_colour = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                return false;
            }
        }
        internal class clsMagicEyeItem : clsMeterItem
        {
            private List<float> _history;
            private int _msHistoryDuration; //ms
            private int _msIgnoreHistoryDuration;
            private bool _showHistory;
            private bool _showValue;
            private readonly object _historyLock = new object();
            private System.Drawing.Color _colour;
            private int _nIgnoringNext;

            private Dictionary<float, PointF> _scaleCalibration;

            public clsMagicEyeItem()
            {
                _history = new List<float>();
                _msHistoryDuration = 500;
                _msIgnoreHistoryDuration = 2000;
                _showHistory = false;
                _showValue = true;

                _colour = System.Drawing.Color.Lime;
                _nIgnoringNext = 0;

                _scaleCalibration = new Dictionary<float, PointF>();

                ItemType = MeterItemType.MAGIC_EYE;
                ReadingSource = Reading.NONE;
                UpdateInterval = 100;
                AttackRatio = 0.8f;
                DecayRatio = 0.2f;
                StoreSettings = false;
            }           
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // get latest reading
                //float reading = MeterManager.getReading(rx, ReadingSource);
                bool use;
                float reading;
                if (MMIOGuid == Guid.Empty)
                {
                    reading = MeterManager.getReading(rx, ReadingSource);
                    use = true;
                }
                else
                {
                    reading = 0;
                    //if (MultiMeterIO.Data.ContainsKey(GetMMIOGuid(0)))
                    if (MultiMeterIO.Data.ContainsKey(MMIOGuid))
                    {
                        //MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[GetMMIOGuid(0)];
                        MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[MMIOGuid];

                        //object val = mmio.GetVariable(GetMMIOVariable(0));
                        object val = mmio.GetVariable(MMIOVariable);
                        if (val is int)
                        {
                            int intVal = (int)val;
                            reading = (float)intVal;
                        }
                        else if (val is float)
                        {
                            reading = (float)val;
                        }
                        else if (val is double)
                        {
                            double doubleVal = (double)val;
                            reading = (float)doubleVal;
                        }
                    }
                    use = false;
                }

                lock (_historyLock)
                {
                    if (reading > Value)
                        Value = (reading * AttackRatio) + (Value * (1f - AttackRatio));
                    else
                        Value = (reading * DecayRatio) + (Value * (1f - DecayRatio));

                    if (_nIgnoringNext <= 0)
                    {
                        // signal history
                        _history.Add(Value); // adds to end of the list
                        int numberToRemove = _history.Count - (_msHistoryDuration / UpdateInterval);
                        // the list is sized based on delay
                        if (numberToRemove > 0) _history.RemoveRange(0, numberToRemove); // remove the oldest, the head of the list
                    }
                    else
                    {
                        _nIgnoringNext--;
                    }
                }

                if (use)
                {
                    // this reading has been used
                    if (!readingsUsed.Contains(ReadingSource))
                        readingsUsed.Add(ReadingSource);
                }
            }
            public override bool ShowHistory
            {
                get { return _showHistory; }
                set { _showHistory = value; }
            }
            public bool ShowValue
            {
                get { return _showValue; }
                set { _showValue = value; }
            }
            public int IgnoreHistoryDuration
            {
                get { return _msIgnoreHistoryDuration; }
                set { _msIgnoreHistoryDuration = value; }
            }
            public int HistoryDuration
            {
                get { return _msHistoryDuration; }
                set
                {
                    _msHistoryDuration = value;
                    if (_msHistoryDuration < UpdateInterval) _msHistoryDuration = UpdateInterval;
                }
            }
            public override float MinHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Min();
                    }
                }
            }
            public override float MaxHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Max();
                    }
                }
            }
            public override void ClearHistory()
            {
                lock (_historyLock)
                {
                    _history.Clear();

                    _nIgnoringNext = _msIgnoreHistoryDuration / UpdateInterval;  // ignore next N readings to make up 2 second of ignore
                }
            }
            public override void History(out float minHistory, out float maxHistory)
            {
                if (_history.Count == 0)
                {
                    minHistory = Value;
                    maxHistory = Value;
                }
                else
                {
                    minHistory = _history.Min();
                    maxHistory = _history.Max();
                }
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set { _colour = value; }
            }
            public override Dictionary<float, PointF> ScaleCalibration
            {
                get { return _scaleCalibration; }
                set { }
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                if (_scaleCalibration != null || _scaleCalibration.Count > 0)
                {
                    float value = _scaleCalibration.OrderBy(p => p.Key).First().Key;

                    if (!values.ContainsKey(ReadingSource))
                        values.Add(ReadingSource, value);
                    else
                        values[ReadingSource] = value;

                    return true;
                }
                return false;
            }
        }
        internal class clsDataOut : clsMeterItem
        {
            public clsDataOut()
            {
                ItemType = MeterItemType.DATA_OUT;
                ReadingSource = Reading.NONE;

                UpdateInterval = 1000;
            }
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                if (all_list_item_readings != null)
                {
                    if (MultiMeterIO.Data.ContainsKey(MMIOGuid))
                    {
                        MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[MMIOGuid];
                        switch(mmio.FormatOut)
                        {
                            case MultiMeterIO.MMIOFormat.JSON:
                                Dictionary<string, string> out_data_converted = new Dictionary<string, string>();
                                out_data_converted.Add("rx", rx.ToString());
                                foreach (KeyValuePair<Reading, object> kvp in all_list_item_readings)
                                {
                                    if(kvp.Value is float flt)
                                        out_data_converted.Add(kvp.Key.ToString().ToLower(), flt.ToString("0.0#####"));
                                    if (kvp.Value is double dbl)
                                        out_data_converted.Add(kvp.Key.ToString().ToLower(), dbl.ToString("0.0#####"));
                                    else if (kvp.Value is string strng)
                                        out_data_converted.Add(kvp.Key.ToString().ToLower(), strng);
                                }
                                string jsonString = JsonConvert.SerializeObject(out_data_converted, Newtonsoft.Json.Formatting.None);
                                MultiMeterIO.SendDataMMIO(MMIOGuid, jsonString);
                                break;
                            case MultiMeterIO.MMIOFormat.XML:
                                XElement root = new XElement("data");
                                XElement entry = new XElement("rx", rx.ToString());
                                root.Add(entry);
                                foreach (KeyValuePair<Reading, object> kvp in all_list_item_readings)
                                {
                                    if (kvp.Value is float flt)
                                        entry = new XElement(kvp.Key.ToString().ToLower(), flt.ToString("0.0#####"));
                                    if (kvp.Value is double dbl)
                                        entry = new XElement(kvp.Key.ToString().ToLower(), dbl.ToString("0.0#####"));
                                    else if (kvp.Value is string strng)
                                        entry = new XElement(kvp.Key.ToString().ToLower(), strng);
                                    root.Add(entry);
                                }
                                MultiMeterIO.SendDataMMIO(MMIOGuid, root.ToString(SaveOptions.DisableFormatting));
                                break;
                            case MultiMeterIO.MMIOFormat.RAW:
                                string out_data = "rx:" + rx.ToString() + ":";
                                foreach (KeyValuePair<Reading, object> kvp in all_list_item_readings)
                                {
                                    Reading reading = kvp.Key;
                                    out_data += reading.ToString().ToLower() + ":";
                                    if (kvp.Value is float flt)
                                        out_data += flt.ToString("0.0#####") + ":";
                                    if (kvp.Value is double dbl)
                                        out_data += dbl.ToString("0.0#####") + ":";
                                    else if (kvp.Value is string strng)
                                        out_data += strng.Replace(":", "") + ":";
                                }
                                MultiMeterIO.SendDataMMIO(MMIOGuid, out_data);
                                break;
                        }
                    }
                }
            }
        }
        internal class clsSpacerItem : clsMeterItem
        {
            private System.Drawing.Color _colour1;
            private System.Drawing.Color _colour2;
            private float _padding;
            public clsSpacerItem()
            {
                _colour1 = System.Drawing.Color.FromArgb(32, 32, 32);
                _colour2 = System.Drawing.Color.FromArgb(32, 32, 32);

                _padding = 0.1f;

                ItemType = MeterItemType.SPACER;
                ReadingSource = Reading.NONE;

                UpdateInterval = 100;
            }
            public System.Drawing.Color Colour1
            {
                get { return _colour1; }
                set { _colour1 = value; }
            }
            public System.Drawing.Color Colour2
            {
                get { return _colour2; }
                set { _colour2 = value; }
            }
            public float Padding
            {
                get { return _padding; }
                set { _padding = value; }
            }
        }
        internal class clsHistoryItem : clsMeterItem
        {
            internal class HistoryData
            {
                public float value;
                public DateTime time;
                public bool show_time;
                public int index;
            }

            private float _padding;
            private float _vertical_ratio;
            private System.Drawing.Color _back_colour;
            private System.Drawing.Color _axis0_colour;
            private System.Drawing.Color _axis1_colour;
            private System.Drawing.Color _lines_colour;
            private System.Drawing.Color _time_colour;
            private int _keep_for;

            // convert to arrays at some point
            private Reading _reading_0;
            private Reading _reading_1;
            private float _min_0;
            private float _min_1;
            private float _max_0;
            private float _max_1;

            private float _min_0_manual;
            private float _min_1_manual;
            private float _max_0_manual;
            private float _max_1_manual;

            private List<HistoryData> _history_data_list_0;
            private List<HistoryData> _history_data_list_1;
            private readonly object _history_data_list_lock_0 = new object();
            private readonly object _history_data_list_lock_1 = new object();

            private int _removed;

            private clsMeter _owningmeter;
            private clsItemGroup _ig;

            private bool _auto_scale_0;
            private bool _auto_scale_1;
            private bool _show_scale_1;

            private int _time_labels_max;
            private int _active_time_labels;
            private DateTime _last_time_label_add_time;
            private bool _next_add_make_time_label;

            private Guid _axis_0_MMIOGuid;
            private string _axis_0_MMIOVariable;
            private Guid _axis_1_MMIOGuid;
            private string _axis_1_MMIOVariable;
            public clsHistoryItem(clsMeter owning_meter, clsItemGroup item_group)
            {
                _owningmeter = owning_meter;
                _ig = item_group;

                _padding = 0.1f;
                _vertical_ratio = 0.5f;
                _back_colour = System.Drawing.Color.Black;
                _axis0_colour = System.Drawing.Color.Red;
                _axis1_colour = System.Drawing.Color.Yellow;
                _lines_colour = System.Drawing.Color.White;
                _time_colour = System.Drawing.Color.Gray;
                _history_data_list_0 = new List<HistoryData>();
                _history_data_list_1 = new List<HistoryData>();
                _reading_0 = Reading.SIGNAL_STRENGTH;
                _reading_1 = Reading.SIGNAL_STRENGTH;
                _keep_for = 60; // seconds

                _min_0 = float.MaxValue;
                _max_0 = float.MinValue;
                _min_1 = float.MaxValue;
                _max_1 = float.MinValue;

                _min_0_manual = -150f;
                _max_0_manual = 0f;
                _min_1_manual = -150f;
                _max_1_manual = 0f;

                _removed = 0;

                _auto_scale_0 = true;
                _auto_scale_1 = true;

                _show_scale_1 = false;

                _time_labels_max = 2; // same as minimum in renderer
                _active_time_labels = 0;
                _last_time_label_add_time = DateTime.UtcNow;
                _next_add_make_time_label = true;

                _axis_0_MMIOGuid = Guid.Empty;
                _axis_0_MMIOVariable = "--DEFAULT--";
                _axis_1_MMIOGuid = Guid.Empty;
                _axis_1_MMIOVariable = "--DEFAULT--";

                ItemType = MeterItemType.HISTORY;

                ReadingSource = Reading.SIGNAL_STRENGTH;

                UpdateInterval = 100;
            }
            //
            public Guid Axis0MMIOGuid
            {
                get { return _axis_0_MMIOGuid; }
                set { _axis_0_MMIOGuid = value; }
            }
            public Guid Axis1MMIOGuid
            {
                get { return _axis_1_MMIOGuid; }
                set { _axis_1_MMIOGuid = value; }
            }
            public string Axis0MMIOVariable
            {
                get { return _axis_0_MMIOVariable; }
                set { _axis_0_MMIOVariable = value; }
            }
            public string Axis1MMIOVariable
            {
                get { return _axis_1_MMIOVariable; }
                set { _axis_1_MMIOVariable = value; }
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                //we dont want to zero anything
                //
                //ZeroReading(out value, rx, _reading_1);
                //value = 0;

                float value = 0;
                ZeroReading(out value, rx, _reading_0);
                if (!values.ContainsKey(_reading_0))
                    values.Add(_reading_0, value);
                else
                    values[_reading_0] = value;

                ZeroReading(out value, rx, _reading_1);
                if (!values.ContainsKey(_reading_1))
                    values.Add(_reading_1, value);
                else
                    values[_reading_1] = value;

                return true;
            }
            public object DataLock0
            {
                get { return _history_data_list_lock_0; }
            }
            public object DataLock1
            {
                get { return _history_data_list_lock_1; }
            }
            public TimeSpan TimeSpanForLables
            {
                get
                {
                    lock (_history_data_list_lock_0)
                    {
                        if (_history_data_list_0.Count > 1)
                        {
                            return _history_data_list_0.Last().time - _history_data_list_0.First().time;
                        }
                        else
                            return new TimeSpan(0);
                    }
                }
            }
            public DateTime LastTimeLabelAddTime
            {
                get { return _last_time_label_add_time; }
            }
            public int ActiveTimeLabels
            {
                get { return _active_time_labels; }
                set { _active_time_labels = value; }
            }
            public int MaxTimeLables
            {
                get
                {
                    lock (_history_data_list_lock_0)
                    {
                        return _time_labels_max;
                    }
                }
                set
                {
                    if(value != _time_labels_max)
                    {
                        _time_labels_max = value;
                    }
                }
            }
            public bool AutoScale0
            {
                get { return _auto_scale_0; }
                set 
                { 
                    _auto_scale_0 = value;
                    if (_auto_scale_0)
                    {
                        lock (_history_data_list_lock_0)
                        {
                            if (_history_data_list_0.Count > 0)
                            {
                                _min_0 = _history_data_list_0.Min(obj => obj.value);
                                _max_0 = _history_data_list_0.Max(obj => obj.value);
                            }
                        }
                    }
                }
            }
            public bool AutoScale1
            {
                get { return _auto_scale_1; }
                set 
                { 
                    _auto_scale_1 = value;
                    if (_auto_scale_1)
                    {
                        lock (_history_data_list_lock_0)
                        {
                            if (_history_data_list_1.Count > 0)
                            {
                                _min_1 = _history_data_list_1.Min(obj => obj.value);
                                _max_1 = _history_data_list_1.Max(obj => obj.value);
                            }
                        }
                    }
                }
            }
            public bool ShowScale1
            {
                get { return _show_scale_1; }
                set { _show_scale_1 = value; }
            }
            public int KeepFor
            {
                get { return _keep_for; }
                set { _keep_for = value; }
            }
            public Reading Reading0
            {
                get { return _reading_0; }
                set 
                {
                    if(_reading_0 != value)
                    {
                        lock (_history_data_list_lock_0)
                        {
                            _history_data_list_0.Clear();
                            _min_0 = float.MaxValue;
                            _max_0 = float.MinValue;
                        }
                        ZeroReading(out float val, _owningmeter.RX, value);
                    }
                    _reading_0 = value;                    
                }
            }
            public Reading Reading1
            {
                get { return _reading_1; }
                set 
                {
                    if (_reading_1 != value)
                    {
                        lock (_history_data_list_lock_1)
                        {
                            _history_data_list_1.Clear();
                            _min_1 = float.MaxValue;
                            _max_1 = float.MinValue;
                        }
                        ZeroReading(out float val, _owningmeter.RX, value);
                    }
                    _reading_1 = value;
                }
            }
            public int ReadingsRemoved
            {
                get { return _removed; }
            }
            //private float test = -140;
            //private bool testadd = true;
            private void addReading(int axis, float value)
            {
                DateTime now = DateTime.UtcNow;
                //strip front of queue, as they will be oldest, for any that are too old
                if (axis == 0)
                {
                    HistoryData hd;
                    lock (_history_data_list_lock_0)
                    {
                        if (_history_data_list_0.Count > 0)
                        {
                            int count = 0;
                            hd = _history_data_list_0[0];
                            while((now - hd.time).TotalSeconds > _keep_for)
                            {
                                if (hd.show_time)
                                {
                                    _active_time_labels--;
                                    if (_active_time_labels < 0) _active_time_labels = 0;
                                }
                                _history_data_list_0.RemoveAt(0);

                                if (_history_data_list_0.Count > 0)
                                {
                                    hd = _history_data_list_0[0];
                                    count++;
                                }
                                else
                                    break;
                            }
                            _removed = count;
                            if (count > 0 && _history_data_list_0.Count > 0)
                            {
                                _min_0 = _history_data_list_0.Min(obj => obj.value);
                                _max_0 = _history_data_list_0.Max(obj => obj.value);
                            }
                        }
                        bool show_time = false;
                        if(_next_add_make_time_label && now.Second % 5 == 0) show_time = true;

                        hd = new HistoryData
                        {
                            value = /*test,*/value,
                            time = now,
                            show_time = show_time
                        };
                        //if (test > 0)
                        //    testadd = false;
                        //else if (test < -140)
                        //    testadd = true;
                        //if (testadd)
                        //    test+=10;
                        //else
                        //    test-=10;

                        if (show_time)
                        {
                            _active_time_labels++;
                            _next_add_make_time_label = false;
                            _last_time_label_add_time = DateTime.UtcNow;
                        }

                        _history_data_list_0.Add(hd);
                        if (value < _min_0) _min_0 = value;
                        if (value > _max_0) _max_0 = value;

                        // time
                        double seconds = (_history_data_list_0[_history_data_list_0.Count - 1].time - _history_data_list_0[0].time).TotalSeconds;
                    }
                }

                if (axis == 1)
                {
                    HistoryData hd;
                    lock (_history_data_list_lock_1)
                    {
                        if (_history_data_list_1.Count > 0)
                        {
                            int count = 0;
                            hd = _history_data_list_1[0];
                            while ((now - hd.time).TotalSeconds > _keep_for)
                            {
                                _history_data_list_1.RemoveAt(0);

                                if (_history_data_list_1.Count > 0)
                                {
                                    hd = _history_data_list_1[0];
                                    count++;
                                }
                                else
                                    break;
                            }
                            _removed = count;
                            if (count > 0 && _history_data_list_1.Count > 0)
                            {
                                _min_1 = _history_data_list_1.Min(obj => obj.value);
                                _max_1 = _history_data_list_1.Max(obj => obj.value);
                            }
                        }
                        hd = new HistoryData
                        {
                            value = value,
                            time = now
                        };
                        _history_data_list_1.Add(hd);
                        if (value < _min_1) _min_1 = value;
                        if (value > _max_1) _max_1 = value;
                    }
                }
            }
            public bool NextAddMakeTimeLabel
            {
                get { return _next_add_make_time_label; }
                set { _next_add_make_time_label = value; }
            }
            public float Range0
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        if (_auto_scale_0)
                            return _max_0 - _min_0;
                        else
                            return _max_0_manual - _min_0_manual;
                    }
                }
            }
            public float Range1
            {
                get 
                {
                    lock (_history_data_list_lock_1)
                    {
                        if (_auto_scale_1)
                            return _max_1 - _min_1;
                        else
                            return _max_1_manual - _min_1_manual;
                    }
                }
            }
            public float Min0
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        if (_auto_scale_0)
                            return _min_0;
                        else
                            return _min_0_manual;
                    }
                }
                set 
                {
                    lock (_history_data_list_lock_0)
                    {
                        _min_0 = value;
                    }
                }
            }
            public float Max0
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        if (_auto_scale_0)
                            return _max_0;
                        else
                            return _max_0_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_0)
                    {
                        _max_0 = value;
                    }
                }
            }
            public float Min1
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        if (_auto_scale_1)
                            return _min_1;
                        else
                            return _min_1_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_1)
                    {
                        _min_1 = value;
                    }
                }
            }
            public float Max1
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        if (_auto_scale_1)
                            return _max_1;
                        else
                            return _max_1_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_1)
                    {
                        _max_1 = value;
                    }
                }
            }
            //
            public float Min0Manual
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        return _min_0_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_0)
                    {
                        _min_0_manual = value;
                    }
                }
            }
            public float Max0Manual
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        return _max_0_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_0)
                    {
                        _max_0_manual = value;
                    }
                }
            }
            public float Min1Manual
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        return _min_1_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_1)
                    {
                        _min_1_manual = value;
                    }
                }
            }
            public float Max1Manual
            {
                get
                {
                    lock (_history_data_list_lock_0)
                    {
                        return _max_1_manual;
                    }
                }
                set
                {
                    lock (_history_data_list_lock_1)
                    {
                        _max_1_manual = value;
                    }
                }
            }
            //
            public float VerticalRatio
            {
                get { return _vertical_ratio; }
                set { _vertical_ratio = value; }
            }
            public System.Drawing.Color BackColour
            {
                get { return _back_colour; }
                set { _back_colour = value; }
            }
            public System.Drawing.Color LinesColour
            {
                get { return _lines_colour; }
                set { _lines_colour = value; }
            }
            public System.Drawing.Color TimeColour
            {
                get { return _time_colour; }
                set { _time_colour = value; }
            }
            public System.Drawing.Color Axis0Colour
            {
                get { return _axis0_colour; }
                set { _axis0_colour = value; }
            }
            public System.Drawing.Color Axis1Colour
            {
                get { return _axis1_colour; }
                set { _axis1_colour = value; }
            }
            public List<HistoryData> History0
            {
                get 
                {
                    lock (_history_data_list_lock_0)
                    {
                        return _history_data_list_0;
                    }
                }
            }
            public List<HistoryData> History1
            {
                get 
                {
                    lock (_history_data_list_lock_1)
                    {
                        return _history_data_list_1;
                    }
                }
            }
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                float reading_0;
                float reading_1;
                //               
                if (_axis_0_MMIOGuid == Guid.Empty)
                {
                    reading_0 = MeterManager.getReading(rx, _reading_0);
                    if (!readingsUsed.Contains(_reading_0))
                        readingsUsed.Add(_reading_0);
                }
                else
                {
                    reading_0 = 0;
                    if (MultiMeterIO.Data.ContainsKey(_axis_0_MMIOGuid))
                    {
                        MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[_axis_0_MMIOGuid];
                        if (mmio == null) return;

                        object val = mmio.GetVariable(_axis_0_MMIOVariable);
                        if (val is int)
                        {
                            int intVal = (int)val;
                            reading_0 = (float)intVal;
                        }
                        else if (val is float)
                        {
                            reading_0 = (float)val;
                        }
                        else if (val is double)
                        {
                            double doubleVal = (double)val;
                            reading_0 = (float)doubleVal;
                        }
                    }
                }
                addReading(0, reading_0);
                //
                if (_show_scale_1)
                {
                    if (_axis_1_MMIOGuid == Guid.Empty)
                    {
                        reading_1 = MeterManager.getReading(rx, _reading_1);
                        if (!readingsUsed.Contains(_reading_1))
                            readingsUsed.Add(_reading_1);
                    }
                    else
                    {
                        reading_1 = 0;
                        if (MultiMeterIO.Data.ContainsKey(_axis_1_MMIOGuid))
                        {
                            MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[_axis_1_MMIOGuid];
                            if (mmio == null) return;

                            object val = mmio.GetVariable(_axis_1_MMIOVariable);
                            if (val is int)
                            {
                                int intVal = (int)val;
                                reading_1 = (float)intVal;
                            }
                            else if (val is float)
                            {
                                reading_1 = (float)val;
                            }
                            else if (val is double)
                            {
                                double doubleVal = (double)val;
                                reading_1 = (float)doubleVal;
                            }
                        }
                    }
                    addReading(1, reading_1);
                }
                //
            }
        }
        internal class clsTextOverlay : clsMeterItem
        {
            private System.Drawing.Color _text_colour1;
            private System.Drawing.Color _text_colour2;
            private System.Drawing.Color _text_back_colour1;
            private System.Drawing.Color _text_back_colour2;
            private System.Drawing.Color _panel_back_colour1;
            private System.Drawing.Color _panel_back_colour2;
            private string _text_1;
            private string _text_2;
            private bool _show_text_back_colour1;
            private bool _show_text_back_colour2;
            private bool _show_back_panel;
            private float _x_offset_1;
            private float _y_offset_1;
            private float _x_offset_2;
            private float _y_offset_2;
            private string _fontFamily_1;
            private FontStyle _fontStyle_1;
            private float _fontSize_1;
            private string _fontFamily_2;
            private FontStyle _fontStyle_2;
            private float _fontSize_2;
            private float _padding;

            private string _band_text;

            private clsMeter _owningMeter;
            private bool _ignore_measure_cache_1;
            private bool _ignore_measure_cache_2;
            private List<string> _list_placeholders_strings_1;
            private List<string> _list_placeholders_strings_2;
            private List<Reading> _list_placeholders_readings_1;
            private List<Reading> _list_placeholders_readings_2;
            private readonly object _list_placeholders_1_lock = new object();
            private readonly object _list_placeholders_2_lock = new object();

            private float _x_scroll;
            private float _x_scroll_offset_1;
            private float _x_scroll_offset_2;
            private int _x_scroll_count_1;
            private int _x_scroll_count_2;

            private string _parsed_text_1;
            private string _parsed_text_2;

            public clsTextOverlay(clsMeter owningMeter)
            {
                _list_placeholders_strings_1 = new List<string>();
                _list_placeholders_strings_2 = new List<string>();
                _list_placeholders_readings_1 = new List<Reading>();
                _list_placeholders_readings_2 = new List<Reading>();

                _text_colour1 = System.Drawing.Color.FromArgb(255, 255, 255);
                _text_colour2 = System.Drawing.Color.FromArgb(255, 255, 255);
                _text_back_colour1 = System.Drawing.Color.FromArgb(64, 64, 64);
                _text_back_colour2 = System.Drawing.Color.FromArgb(64, 64, 64);
                _panel_back_colour1 = System.Drawing.Color.FromArgb(32, 32, 32);
                _panel_back_colour2 = System.Drawing.Color.FromArgb(32, 32, 32);
                _text_1 = "";
                _text_2 = "";
                _show_text_back_colour1 = false;
                _show_text_back_colour2 = false;
                _show_back_panel = true;
                _x_offset_1 = 0;
                _y_offset_1 = 0;
                _x_offset_2 = 0;
                _y_offset_2 = 0;

                _x_scroll = -0.15f;
                _x_scroll_offset_1 = 0;
                _x_scroll_offset_2 = 0;
                _x_scroll_count_1 = 0;
                _x_scroll_count_2 = 0;

                _parsed_text_1 = "";
                _parsed_text_2 = "";

                ItemType = MeterItemType.TEXT_OVERLAY;
                ReadingSource = Reading.NONE;

                _fontFamily_1 = "Trebuchet MS";
                _fontStyle_1 = FontStyle.Regular;
                _fontSize_1 = 18f;
                _fontFamily_2 = "Trebuchet MS";
                _fontStyle_2 = FontStyle.Regular;
                _fontSize_2 = 18f;

                _padding = 0.1f;

                _band_text = "";

                _ignore_measure_cache_1 = false;
                _ignore_measure_cache_2 = false;

                _owningMeter = owningMeter;

                UpdateInterval = 100;
            }
            public int ScrollXCount1
            {
                get { return _x_scroll_count_1; }
                set { _x_scroll_count_1 = value; }
            }
            public int ScrollXCount2
            {
                get { return _x_scroll_count_2; }
                set { _x_scroll_count_2 = value; }
            }
            public float ScrollXOffset1
            {
                get { return _x_scroll_offset_1; }
                set { _x_scroll_offset_1 = value; }
            }
            public float ScrollXOffset2
            {
                get { return _x_scroll_offset_2; }
                set { _x_scroll_offset_2 = value; }
            }
            public float ScrollX
            {
                get { return _x_scroll; }
                set { _x_scroll = value; }
            }
            public System.Drawing.Color TextColour1
            {
                get { return _text_colour1; }
                set { _text_colour1 = value; }
            }
            public System.Drawing.Color TextColour2
            {
                get { return _text_colour2; }
                set { _text_colour2 = value; }
            }
            public System.Drawing.Color TextBackColour1
            {
                get { return _text_back_colour1; }
                set { _text_back_colour1 = value; }
            }
            public System.Drawing.Color TextBackColour2
            {
                get { return _text_back_colour2; }
                set { _text_back_colour2 = value; }
            }
            public string Text1
            {
                get { return _text_1;/*.Replace("|", "");*/ }
                set
                {
                    value = string.IsNullOrEmpty(value) ? "" : value;
                    _ignore_measure_cache_1 = _text_1 != value;
                    _text_1 = string.IsNullOrEmpty(value) ? "" : value;//.Replace("|", ""); // dont need to replace this now due to new store/restore
                    ReadingsCustom(_owningMeter.RX).UpdateReadings(_text_1);
                    lock (_list_placeholders_1_lock)
                    {
                        _list_placeholders_readings_1.Clear();
                        _list_placeholders_strings_1.Clear();
                        List<string> placeholders = ReadingsCustom(_owningMeter.RX).GetPlaceholders(_text_1);
                        foreach (string placeholder in placeholders)
                        {
                            if (ReadingsCustom(_owningMeter.RX).IsCustomString(placeholder) || placeholder.StartsWith("precis="))
                                _list_placeholders_strings_1.Add(placeholder);
                            else
                            {
                                bool ok = Enum.TryParse<Reading>(placeholder.ToUpper(), out Reading tmpReading);
                                if (ok) _list_placeholders_readings_1.Add(tmpReading);
                            }
                        }

                        string old = _parsed_text_1;
                        _parsed_text_1 = parseText1();
                        if (_parsed_text_1 != old) _x_scroll_count_1 = 0;
                    }
                }
            }
            public string Text2
            {
                get { return _text_2;/*.Replace("|", "");*/ }
                set 
                {
                    value = string.IsNullOrEmpty(value) ? "" : value;
                    _ignore_measure_cache_2 = _text_2 != value;
                    _text_2 = string.IsNullOrEmpty(value) ? "" : value;//.Replace("|", ""); // dont need to replace this now due to new store/restore
                    ReadingsCustom(_owningMeter.RX).UpdateReadings(_text_2);
                    lock (_list_placeholders_2_lock)
                    {
                        _list_placeholders_readings_2.Clear();
                        _list_placeholders_strings_2.Clear();
                        List<string> placeholders = ReadingsCustom(_owningMeter.RX).GetPlaceholders(_text_2);
                        foreach(string placeholder in placeholders)
                        {
                            if (ReadingsCustom(_owningMeter.RX).IsCustomString(placeholder) || placeholder.StartsWith("precis="))
                                _list_placeholders_strings_2.Add(placeholder);
                            else
                            {
                                bool ok = Enum.TryParse<Reading>(placeholder.ToUpper(), out Reading tmpReading);
                                if (ok) _list_placeholders_readings_2.Add(tmpReading);
                            }
                        }

                        string old = _parsed_text_2;
                        _parsed_text_2 = parseText2();
                        if (_parsed_text_2 != old) _x_scroll_count_2 = 0;
                    }
                }
            }
            public string ParsedText1
            {
                get {
                    lock (_list_placeholders_1_lock)
                    { 
                        return _parsed_text_1;
                    }
                }
            }
            public string ParsedText2
            {
                get {
                    lock (_list_placeholders_2_lock)
                    {
                        return _parsed_text_2;
                    }
                }
            }
            public bool ShowTextBackColour1
            {
                get { return _show_text_back_colour1; }
                set { _show_text_back_colour1 = value; }
            }
            public bool ShowTextBackColour2
            {
                get { return _show_text_back_colour2; }
                set { _show_text_back_colour2 = value; }
            }
            public bool ShowBackPanel
            {
                get { return _show_back_panel; }
                set { _show_back_panel = value; }
            }
            public System.Drawing.Color PanelBackColour1
            {
                get { return _panel_back_colour1; }
                set { _panel_back_colour1 = value; }
            }
            public System.Drawing.Color PanelBackColour2
            {
                get { return _panel_back_colour2; }
                set { _panel_back_colour2 = value; }
            }
            public float TextXOffset1
            {
                get { return _x_offset_1; }
                set { _x_offset_1 = value; }
            }
            public float TextYOffset1
            {
                get { return _y_offset_1; }
                set { _y_offset_1 = value; }
            }
            public float TextXOffset2
            {
                get { return _x_offset_2; }
                set { _x_offset_2 = value; }
            }
            public float TextYOffset2
            {
                get { return _y_offset_2; }
                set { _y_offset_2 = value; }
            }
            public string FontFamily1
            {
                get { return _fontFamily_1; }
                set { _fontFamily_1 = value; }
            }
            public FontStyle Style1
            {
                get { return _fontStyle_1; }
                set { _fontStyle_1 = value; }
            }
            public float FontSize1
            {
                get { return _fontSize_1; }
                set { _fontSize_1 = value; }
            }
            public string FontFamily2
            {
                get { return _fontFamily_2; }
                set { _fontFamily_2 = value; }
            }
            public FontStyle Style2
            {
                get { return _fontStyle_2; }
                set { _fontStyle_2 = value; }
            }
            public float FontSize2
            {
                get { return _fontSize_2; }
                set { _fontSize_2 = value; }
            }
            public float Padding
            {
                get { return _padding; }
                set { _padding = value; }
            }
            public bool IgnoreMeasureCache1
            {
                get { return _ignore_measure_cache_1; }
                set { _ignore_measure_cache_1 = value; }
            }
            public bool IgnoreMeasureCache2
            {
                get { return _ignore_measure_cache_2; }
                set { _ignore_measure_cache_2 = value; }
            }
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                lock (_list_placeholders_1_lock)
                {
                    foreach(Reading reading in _list_placeholders_readings_1)
                    {
                        ReadingsCustom(_owningMeter.RX).TakeReading(reading);

                    }
                    string old = _parsed_text_1;
                    _parsed_text_1 = parseText1();
                    if (_parsed_text_1 != old) _x_scroll_count_1 = 0;
                }
                lock (_list_placeholders_2_lock)
                {
                    foreach (Reading reading in _list_placeholders_readings_2)
                    {
                        ReadingsCustom(_owningMeter.RX).TakeReading(reading);

                    }
                    string old = _parsed_text_2;
                    _parsed_text_2 = parseText2();
                    if (_parsed_text_2 != old) _x_scroll_count_2 = 0;
                }
            }
            private string parseText1()
            {
                string sTmp = _text_1;
                string lower;
                bool precis_found = false;
                string precision_format = "0.0#####";//"f6";

                lock (_list_placeholders_1_lock)
                {
                    foreach (string placeholder in _list_placeholders_strings_1)
                    {
                        lower = "%" + placeholder.ToLower() + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            if(lower.IndexOf("%precis=") >= 0)
                            {
                                sTmp = sTmp.Replace(lower, "");
                                if (!precis_found)
                                {
                                    int startIndex = lower.IndexOf('=') + 1;
                                    int endIndex = lower.Length - 1;
                                    string numberString = lower.Substring(startIndex, endIndex - startIndex);
                                    if (numberString.Length > 0)
                                    {
                                        bool ok = int.TryParse(numberString, out int precis);
                                        if (ok)
                                        {
                                            if (precis > 20) precis = 20;
                                            if (precis < 0) precis = 0;
                                            precision_format = $"f{precis}";
                                            precis_found = true;
                                            continue;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    foreach (string placeholder in _list_placeholders_strings_1)
                    {
                        lower = "%" + placeholder.ToLower() + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            string decFormat = precis_found ? precision_format : "0.0#####";
                            object reading = ReadingsCustom(_owningMeter.RX).GetReading(placeholder, _owningMeter);
                            if (reading is int)
                                sTmp = sTmp.Replace(lower, ((int)reading).ToString());
                            else if (reading is float)
                                sTmp = sTmp.Replace(lower, ((float)reading).ToString(decFormat));
                            else if (reading is double)
                                sTmp = sTmp.Replace(lower, ((double)reading).ToString(decFormat));
                            else if (reading is bool)
                                sTmp = sTmp.Replace(lower, ((bool)reading).ToString());
                            else
                                sTmp = sTmp.Replace(lower, (string)reading);
                        }
                    }
                    foreach (Reading r in _list_placeholders_readings_1)
                    {
                        lower = "%" + r.ToString().ToLower() + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            object reading = ReadingsCustom(_owningMeter.RX).GetReading(r.ToString(), _owningMeter);
                            sTmp = sTmp.Replace(lower, ((float)reading).ToString(precis_found ? precision_format : "0.0#####"));
                        }
                    }
                }

                if (sTmp.IndexOf("%nl%") >= 0)
                    sTmp = sTmp.Replace("%nl%", "\n");

                // MultiMeter IO
                foreach (KeyValuePair<Guid, MultiMeterIO.clsMMIO> mmios in MultiMeterIO.Data)
                {
                    MultiMeterIO.clsMMIO mmio = mmios.Value;
                    foreach (KeyValuePair<string, object> kvp in mmio.Variables())
                    {
                        lower = "%" + kvp.Key + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            object val = mmio.GetVariable(kvp.Key, precis_found ? precision_format : "");

                            string tmp = mmio.VariableValueType(val, precis_found ? precision_format : "");

                            sTmp = sTmp.Replace(lower, tmp);
                        }
                    }
                }
                //

                return sTmp;
            }
            private string parseText2()
            {
                string sTmp = _text_2;
                string lower;
                bool precis_found = false;
                string precision_format = "0.0#####";//"f6";

                lock (_list_placeholders_2_lock)
                {
                    foreach (string placeholder in _list_placeholders_strings_2)
                    {
                        lower = "%" + placeholder.ToLower() + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            if(lower.IndexOf("%precis=") >= 0)
                            {
                                sTmp = sTmp.Replace(lower, "");
                                if (!precis_found)
                                {
                                    int startIndex = lower.IndexOf('=') + 1;
                                    int endIndex = lower.Length - 1;
                                    string numberString = lower.Substring(startIndex, endIndex - startIndex);
                                    if (numberString.Length > 0)
                                    {
                                        bool ok = int.TryParse(numberString, out int precis);
                                        if (ok)
                                        {
                                            if (precis > 20) precis = 20;
                                            if (precis < 0) precis = 0;
                                            precision_format = $"f{precis}";
                                            precis_found = true;
                                            continue;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    foreach (string placeholder in _list_placeholders_strings_2)
                    {
                        lower = "%" + placeholder.ToLower() + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                             string decFormat = precis_found ? precision_format : "0.0#####";
                            object reading = ReadingsCustom(_owningMeter.RX).GetReading(placeholder, _owningMeter);
                            if (reading is int)
                                sTmp = sTmp.Replace(lower, ((int)reading).ToString());
                            else if (reading is float)
                                sTmp = sTmp.Replace(lower, ((float)reading).ToString(decFormat));
                            else if (reading is double)
                                sTmp = sTmp.Replace(lower, ((double)reading).ToString(decFormat));
                            else if (reading is bool)
                                sTmp = sTmp.Replace(lower, ((bool)reading).ToString());
                            else
                                sTmp = sTmp.Replace(lower, (string)reading);
                        }
                    }
                    foreach (Reading r in _list_placeholders_readings_2)
                    {
                        lower = "%" + r.ToString().ToLower() + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            object reading = ReadingsCustom(_owningMeter.RX).GetReading(r.ToString(), _owningMeter);
                            sTmp = sTmp.Replace(lower, ((float)reading).ToString(precis_found ? precision_format : "0.0#####"));
                        }
                    }
                }

                if (sTmp.IndexOf("%nl%") >= 0)
                    sTmp = sTmp.Replace("%nl%", "\n");

                // MultiMeter IO
                foreach (KeyValuePair<Guid, MultiMeterIO.clsMMIO> mmios in MultiMeterIO.Data)
                {
                    MultiMeterIO.clsMMIO mmio = mmios.Value;
                    foreach (KeyValuePair<string, object> kvp in mmio.Variables())
                    {
                        lower = "%" + kvp.Key + "%";
                        if (sTmp.IndexOf(lower) >= 0)
                        {
                            object val = mmio.GetVariable(kvp.Key, precis_found ? precision_format : "");

                            string tmp = mmio.VariableValueType(val, precis_found ? precision_format : "");

                            sTmp = sTmp.Replace(lower, tmp);
                        }
                    }
                }
                //

                return sTmp;
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                lock (_list_placeholders_1_lock)
                {
                    foreach (Reading reading in _list_placeholders_readings_1)
                    {
                        float value = 0;
                        ZeroReading(out value, rx, reading);

                        if (!values.ContainsKey(reading))
                            values.Add(reading, value);
                        else
                            values[reading] = value;
                    }
                }
                lock (_list_placeholders_2_lock)
                {
                    foreach (Reading reading in _list_placeholders_readings_2)
                    {
                        float value = 0;
                        ZeroReading(out value, rx, reading);

                        if (!values.ContainsKey(reading))
                            values.Add(reading, value);
                        else
                            values[reading] = value;
                    }
                }
                return true;

                //value = 0;
                //lock (_list_placeholders_1_lock)
                //{
                //    foreach(Reading reading in _list_placeholders_readings_1)
                //    {
                //        ZeroReading(out value, rx, reading);
                //        MeterManager.setReadingForced(rx, reading, value);
                //    }
                //}
                //lock (_list_placeholders_2_lock)
                //{
                //    foreach (Reading reading in _list_placeholders_readings_2)
                //    {
                //        ZeroReading(out value, rx, reading);
                //        MeterManager.setReadingForced(rx, reading, value);
                //    }
                //}
                //return false; // false as we do our own setReadingForced just above
            }
        }
        internal class clsLed : clsMeterItem
        {
            public enum Led_Shape
            {
                SQUARE = 0,
                ROUND = 1,
                TRIANGLE = 2
            }
            public enum Led_Style
            {
                FLAT = 0,
                THREE_D = 1
            }
            private System.Drawing.Color _true_colour;
            private System.Drawing.Color _false_colour;
            private System.Drawing.Color _panel_back_colour_1;
            private System.Drawing.Color _panel_back_colour_2;
            private string _condition;
            private string _pending_condition;
            private bool _show_back_panel;
            private float _x_offset;
            private float _y_offset;
            private float _x_size;
            private float _y_size;
            private float _padding;
            private clsMeter _owningMeter;
            private Led_Shape _led_shape;
            private Led_Style _led_style;
            private bool _old_result;
            private bool _result;
            private bool _valid;
            private List<string> _list_placeholders_strings;
            private List<Reading> _list_placeholders_readings;
            private readonly object _list_placeholders_lock = new object();
            private Script _script;
            private Dictionary<string, object> _variable_substitutions;
            private bool _busy = false;
            private bool _error = false;
            CancellationTokenSource _cts;
            private bool _forceRecompile;
            private System.Threading.Timer _timer;
            private int _delay_milliseconds;

            private bool _blink;
            private bool _pulsate;
            private bool _pulsate_up;
            private int _transition_fade;
            private float _color_fade;
            private int _blink_count;
            private bool _show_false;
            private bool _show_true;

            private bool _notxtrue;
            private bool _notxfalse;

            private Thread _thread;

            public clsLed(clsMeter owningMeter)
            {
                _timer = null;
                _delay_milliseconds = 1000;

                _list_placeholders_strings = new List<string>();
                _list_placeholders_readings = new List<Reading>();

                _true_colour = System.Drawing.Color.FromArgb(255, 255, 255);
                _false_colour = System.Drawing.Color.FromArgb(192, 192, 192);
                _panel_back_colour_1 = System.Drawing.Color.FromArgb(32, 32, 32);
                _panel_back_colour_2 = System.Drawing.Color.FromArgb(32, 32, 32);
                _show_back_panel = true;
                _x_offset = 0.5f;
                _y_offset = 0.05f;
                _condition = "";
                _pending_condition = "";
                _x_size = 0.05f;
                _y_size = 0.05f;
                _padding = 0.1f;
                _led_shape = Led_Shape.SQUARE;
                _led_style = Led_Style.FLAT;
                _old_result = false;
                _result = false;
                _valid = false;
                _busy = false;
                _error = false;
                _notxtrue = false;
                _notxtrue = false;

                ItemType = MeterItemType.LED;
                ReadingSource = Reading.NONE;

                _owningMeter = owningMeter;

                UpdateInterval = 100;

                _cts = null;
                _script = null;
                _variable_substitutions = new Dictionary<string, object>();

                _forceRecompile = false;

                _pulsate_up = true;
                _pulsate = false;
                _blink = false;
                _transition_fade = 0;
                _color_fade = 0;
                _blink_count = 0;

                _show_true = true;
                _show_false = true;
            }
            private bool validateExpression(string expression, Dictionary<string, object> variables)
            {
                string tmp = $"bool result = (bool)({expression});";

                try
                {
                    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(tmp);
                    CSharpCompilation compilation = CSharpCompilation.Create("ExpressionValidation")
                        .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
                        .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
                        //.AddReferences(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location))
                        //.AddReferences(MetadataReference.CreateFromFile(typeof(Console).Assembly.Location))
                        .AddSyntaxTrees(syntaxTree);
                    IEnumerable<Diagnostic> diagnostics = compilation.GetDiagnostics();
                    foreach (Diagnostic diagnostic in diagnostics)
                    {
                        if (diagnostic.Severity == DiagnosticSeverity.Error)
                        {
                            return false;
                        }
                    }
                    return true;
                }
                catch (Exception ex)
                {
                    //MessageBox.Show($"Exception: {ex.Message}");
                    //MessageBox.Show($"String passed to Parse Text:\n{tmp}\n\nStack Trace: {ex.StackTrace}");
                    return false;
                }
            }
            private (bool,bool) evaluateExpression(CancellationToken cancellationToken)
            {
                if (!_valid || _script == null || _busy) return (false, false);                

                bool bRet = false;
                try
                {
                    Globals globals = new Globals();
                    globals.Variables = _variable_substitutions;
                    ScriptState ss = _script.RunAsync(globals, cancellationToken).GetAwaiter().GetResult();
                    if (ss.Variables.Length == 1)
                    {
                        ScriptVariable sv = ss.Variables[0];
                        bRet = (bool)sv.Value;
                        _error = false;
                    }
                    else
                    {
                        _valid = false;
                        _script = null;
                        _error = true;
                    }
                }
                catch (Exception ex)
                {
                    _valid = false;
                    _script = null;
                    _error = true;
                }

                return (true, bRet);
            }
            public bool NoTxFalse
            {
                get { return _notxfalse; }
                set { _notxfalse = value; }
            }
            public bool NoTxTrue
            {
                get { return _notxtrue; }
                set { _notxtrue = value; }
            }
            public bool ScriptError
            {
                get { return _error; }
            }
            public bool ScriptValid
            {
                get { return _valid; }
            }
            public bool ConditionResult
            {
                get { return _result; }
            }
            public bool OldConditionResult
            {
                get { return _old_result; }
            }
            public System.Drawing.Color PanelBackColour1
            {
                get { return _panel_back_colour_1; }
                set { _panel_back_colour_1 = value; }
            }
            public System.Drawing.Color PanelBackColour2
            {
                get { return _panel_back_colour_2; }
                set { _panel_back_colour_2 = value; }
            }
            public bool ShowTrue
            {
                get { return _show_true; }
                set { _show_true = value; }
            }
            public bool ShowFalse
            {
                get { return _show_false; }
                set { _show_false = value; }
            }
            public Led_Shape LedShape
            {
                get { return _led_shape; }
                set { _led_shape = value; }
            }
            public Led_Style LedStyle
            {
                get { return _led_style; }
                set { _led_style = value; }
            }

            private void onTimerElapsedCondition()
            {                
                _condition = string.IsNullOrEmpty(_pending_condition) ? "" : _pending_condition;
                ReadingsCustom(_owningMeter.RX).UpdateReadings(_condition);
                lock (_list_placeholders_lock)
                {
                    _list_placeholders_readings.Clear();
                    _list_placeholders_strings.Clear();
                    List<string> placeholders = ReadingsCustom(_owningMeter.RX).GetPlaceholders(_condition);
                    foreach (string placeholder in placeholders)
                    {
                        if (ReadingsCustom(_owningMeter.RX).IsCustomString(placeholder))
                            _list_placeholders_strings.Add(placeholder);
                        else
                        {
                            bool ok = Enum.TryParse<Reading>(placeholder.ToUpper(), out Reading tmpReading);
                            if (ok) _list_placeholders_readings.Add(tmpReading);
                        }
                    }

                    _variable_substitutions.Clear();
                    string expression = _condition;
                    string script_expression = _condition;
                    string lower;
                    foreach (Reading r in _list_placeholders_readings)
                    {
                        object reading = ReadingsCustom(_owningMeter.RX).GetReading(r.ToString(), _owningMeter);
                        lower = "%" + r.ToString().ToLower() + "%";
                        if (expression.IndexOf(lower) >= 0)
                            expression = expression.Replace(lower, reading.ToString());
                        if (script_expression.IndexOf(lower) >= 0)
                            script_expression = script_expression.Replace(lower, "(float)Variables[\"" + r.ToString().ToLower() + "\"]");

                        if (!_variable_substitutions.ContainsKey(r.ToString().ToLower()))
                            _variable_substitutions.Add(r.ToString().ToLower(), reading);
                    }
                    foreach (string placeholder in _list_placeholders_strings)
                    {
                        object reading = ReadingsCustom(_owningMeter.RX).GetReading(placeholder, _owningMeter);
                        string type;
                        if (reading is int)
                            type = "int";
                        else if (reading is float)
                            type = "float";
                        else if (reading is double)
                            type = "double";
                        else if (reading is bool)
                            type = "bool";
                        else
                            type = "string";

                        lower = "%" + placeholder.ToLower() + "%";
                        if (expression.IndexOf(lower) >= 0)
                            expression = expression.Replace(lower, "((" + type + ")(" + (type == "string" ? "\"" : "") + reading.ToString() + (type == "string" ? "\"" : "") + "))");
                        if (script_expression.IndexOf(lower) >= 0)
                            script_expression = script_expression.Replace(lower, "((" + type + ")(Variables[\"" + placeholder.ToLower() + "\"]))");

                        if (!_variable_substitutions.ContainsKey(placeholder.ToLower()))
                            _variable_substitutions.Add(placeholder.ToLower(), reading);
                    }

                    // MultiMeter IO
                    foreach (KeyValuePair<Guid, MultiMeterIO.clsMMIO> mmios in MultiMeterIO.Data)
                    {
                        MultiMeterIO.clsMMIO mmio = mmios.Value;
                        foreach (KeyValuePair<string, object> kvp in mmio.Variables())
                        {
                            object val = mmio.GetVariable(kvp.Key);

                            string tmp = mmio.VariableValueType(val);
                            lower = "%" + kvp.Key + "%";
                            if (script_expression.IndexOf(lower) >= 0)
                            {
                                string type;
                                if (val is int)
                                    type = "int";
                                else if (val is float)
                                    type = "float";
                                else if (val is double)
                                    type = "double";
                                else if (val is bool)
                                    type = "bool";
                                else
                                    type = "string";

                                if (expression.IndexOf(lower) >= 0)
                                    expression = expression.Replace(lower, (type == "string" ? "\"" : "") + tmp + (type == "string" ? "\"" : ""));
                                if (script_expression.IndexOf(lower) >= 0)
                                    script_expression = script_expression.Replace(lower, "((" + type + ")(Variables[\"" + kvp.Key + "\"]))");

                                if (!_variable_substitutions.ContainsKey(kvp.Key))
                                    _variable_substitutions.Add(kvp.Key, val);
                            }
                        }
                    }
                    //

                    bool okExp = validateExpression(expression, _variable_substitutions);
                    if (okExp)
                    {
                        if (_cts != null)
                        {
                            _cts.Cancel();
                            _cts.Dispose();
                            _cts = null;
                        }
                        try
                        {
                            //ScriptOptions options = ScriptOptions.Default.AddReferences(typeof(object).Assembly);
                            ScriptOptions options = ScriptOptions.Default
                                                                    .AddReferences(typeof(object).Assembly)
                                                                    .AddReferences(typeof(string).Assembly);
                                                                    //.AddImports("System");
                            _script = CSharpScript.Create($"bool result = (bool)({script_expression});", options, typeof(Globals));
                            _script.Compile();                            

                            _valid = true;
                        }
                        catch
                        {
                            _script = null;
                            _valid = false;
                        }
                    }
                    else
                    {
                        _script = null;
                        _valid = false;
                    }
                }
                
                _forceRecompile = false;
            }
            public string Condition
            {
                get { return _pending_condition; }
                set {
                    if (value == _condition && !_forceRecompile) return;

                    _pending_condition = value;
                    _pending_condition = string.IsNullOrEmpty(_pending_condition) ? "" : _pending_condition;

                    if (_timer == null)
                        _timer = new System.Threading.Timer(_ => onTimerElapsedCondition(), null, _delay_milliseconds, Timeout.Infinite);
                    else
                        _timer.Change(_forceRecompile ? 0 : _delay_milliseconds, Timeout.Infinite);                                     
                }
            }
            public float OffsetX
            {
                get { return _x_offset; }
                set { _x_offset = value; }
            }
            public float OffsetY
            {
                get { return _y_offset; }
                set { _y_offset = value; }
            }
            public float SizeX
            {
                get { return _x_size; }
                set { _x_size = value; }
            }
            public float SizeY
            {
                get { return _y_size; }
                set { _y_size = value; }
            }
            public float ColorFade
            {
                get { return _color_fade; }
                set {
                    if (value < 0) value = 0;
                    if (value > 1) value = 1;
                    _color_fade = value; 
                }
            }
            public int TransitionFade
            {
                get { return _transition_fade; }
                set { _transition_fade = value; }
            }
            public bool Pulsate
            {
                get { return _pulsate; }
                set { _pulsate = value; }
            }
            public bool PulsateUp
            {
                get { return _pulsate_up; }
                set { _pulsate_up = value; }
            }
            public bool Blink
            {
                get { return _blink; }
                set { _blink = value; }
            }
            public int BlinkCount
            {
                get { return _blink_count; }
                set { _blink_count = value; }
            }
            public System.Drawing.Color TrueColour
            {
                get { return _true_colour; }
                set { _true_colour = value; }
            }
            public System.Drawing.Color FalseColour
            {
                get { return _false_colour; }
                set { _false_colour = value; }
            }
            public bool ShowBackPanel
            {
                get { return _show_back_panel; }
                set { _show_back_panel = value; }
            }
            public float Padding
            {
                get { return _padding; }
                set { _padding = value; }
            }
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                if (!_busy && _valid && _script != null && !_forceRecompile)
                {
                    bool typesChanged = false;

                    lock (_list_placeholders_lock)
                    {
                        // regular readings
                        foreach (Reading r in _list_placeholders_readings)
                        {
                            if (_variable_substitutions.ContainsKey(r.ToString().ToLower()))
                            {
                                ReadingsCustom(_owningMeter.RX).TakeReading(r);
                                object reading = ReadingsCustom(_owningMeter.RX).GetReading(r.ToString(), _owningMeter);
                                _variable_substitutions[r.ToString().ToLower()] = (float)reading;
                            }
                        }
                        // readings with string as ID
                        foreach (string placeholder in _list_placeholders_strings)
                        {
                            if (_variable_substitutions.ContainsKey(placeholder.ToLower()))
                            {
                                object reading = ReadingsCustom(_owningMeter.RX).GetReading(placeholder, _owningMeter);
                                _variable_substitutions[placeholder.ToLower()] = reading;
                            }
                        }
                    }

                    // MultiMeter IO readings
                    foreach (KeyValuePair<Guid, MultiMeterIO.clsMMIO> mmios in MultiMeterIO.Data)
                    {
                        MultiMeterIO.clsMMIO mmio = mmios.Value;
                        foreach (KeyValuePair<string, object> kvp in mmio.Variables())
                        {
                            if (_variable_substitutions.ContainsKey(kvp.Key))
                            {
                                object var = mmio.GetVariable(kvp.Key);
                                if(var.GetType() != _variable_substitutions[kvp.Key].GetType())
                                {
                                    typesChanged = true;
                                    break;
                                }
                                _variable_substitutions[kvp.Key] = var;
                            }
                        }
                    }
                    //

                    if (typesChanged)
                    {
                        Debug.Print(">>>> RECOMPILE");
                        _forceRecompile = true;
                        Condition = Condition; // force recompile
                    }
                    else
                    {
                        _busy = true;
                        _cts = new CancellationTokenSource();
                        _old_result = _result;

                        Task.Run(() =>
                        {
                            (bool valid, bool result) = evaluateExpression(_cts.Token);
                            if (valid)
                            {
                                _result = result;

                                // mox
                                if (_result && _notxtrue && _console.MOX)
                                {
                                    stopMox();
                                }
                                else if (!_result && _notxfalse && _console.MOX)
                                {
                                    stopMox();
                                }
                            }
                        });
                        _busy = false;
                    }
                }
            }
            private void stopMox()
            {
                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    _console.StopAllTx("Led Indicator stopped MOX");
                }));
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                lock (_list_placeholders_lock)
                {
                    foreach (Reading reading in _list_placeholders_readings)
                    {
                        float value = 0;
                        ZeroReading(out value, rx, reading);

                        if (!values.ContainsKey(reading))
                            values.Add(reading, value);
                        else
                            values[reading] = value;
                    }
                }
                return true;
            }
        }
        internal class clsBarItem : clsMeterItem
        {
            [Serializable]
            public enum Units
            {
                DBM = 0,
                S_UNTS,
                U_V
            }
            [Serializable]
            public enum BarStyle
            {
                None = 0,
                Line,
                SolidFilled,
                GradientFilled,
                Segments,
            }
            private List<float> _history;
            private int _msHistoryDuration; //ms
            private int _msIgnoreHistoryDuration;
            private bool _showHistory;
            private bool _showValue;
            private bool _showPeakValue;
            private readonly object _historyLock = new object();
            private BarStyle _style;
            private System.Drawing.Color _colour;
            private System.Drawing.Color _colourHigh;
            private System.Drawing.Color _markerColour;
            private System.Drawing.Color _peakHoldMarkerColour;
            private System.Drawing.Color _peakValueColour;
            private float _strokeWidth;
            private bool _peakHold;
            private bool _showMarker;
            private Units _units;

            private string _fontFamily;
            private FontStyle _fontStyle;
            private System.Drawing.Color _fontColor;
            private float _fontSize;
            private int _nIgnoringNext;

            private Dictionary<float, PointF> _scaleCalibration;
            private PointF _highPoint;

            private clsBarItem _postDrawItem;

            public clsBarItem()
            {
                _history = new List<float>();
                _msHistoryDuration = 2000;
                _msIgnoreHistoryDuration = 2000;
                _showHistory = false;
                _showValue = true;
                _showPeakValue = true;
                _nIgnoringNext = 0;

                _style = BarStyle.Line;
                _colour = System.Drawing.Color.Red;
                _colourHigh = System.Drawing.Color.Orange;
                _markerColour = System.Drawing.Color.Yellow;
                _peakHoldMarkerColour = System.Drawing.Color.Red;
                _peakValueColour = System.Drawing.Color.Red;
                _strokeWidth = 3f;
                _peakHold = false;
                _showMarker = true;
                _highPoint = PointF.Empty;
                
                _units = Units.DBM;

                _scaleCalibration = new Dictionary<float, PointF>();

                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontColor = System.Drawing.Color.DarkGray;
                _fontSize = 20f;

                ItemType = MeterItemType.H_BAR;
                ReadingSource = Reading.NONE;
                UpdateInterval = 100;
                AttackRatio = 0.8f;
                DecayRatio = 0.2f;
                StoreSettings = false;

                _postDrawItem = null; // used in render loop to cause another bar's marker to be re-drawn after this bar
            }            
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // get latest reading
                float reading;
                bool use;
                
                if(MMIOGuid == Guid.Empty)
                {
                    reading = MeterManager.getReading(rx, ReadingSource);
                    use = true;
                }
                else
                {
                    reading = 0;
                    //if (MultiMeterIO.Data.ContainsKey(GetMMIOGuid(0)))
                    if (MultiMeterIO.Data.ContainsKey(MMIOGuid))
                    {
                        //MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[GetMMIOGuid(0)];
                        MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[MMIOGuid];

                        //object val = mmio.GetVariable(GetMMIOVariable(0));
                        object val = mmio.GetVariable(MMIOVariable);
                        if (val is int)
                        {
                            int intVal = (int)val;
                            reading = (float)intVal;
                        }
                        else if (val is float)
                        {
                            reading = (float)val;
                        }
                        else if (val is double)
                        {
                            double doubleVal = (double)val;
                            reading = (float)doubleVal;
                        }
                    }
                    use = false;
                }

                lock (_historyLock)
                {
                    if (reading > Value)
                        Value = (reading * AttackRatio) + (Value * (1f - AttackRatio));
                    else
                        Value = (reading * DecayRatio) + (Value * (1f - DecayRatio));

                    if (_nIgnoringNext <= 0)
                    {
                        // signal history
                        _history.Add(Value); // adds to end of the list
                        int numberToRemove = _history.Count - (_msHistoryDuration / UpdateInterval);
                        // the list is sized based on delay
                        if (numberToRemove > 0) _history.RemoveRange(0, numberToRemove); // remove the oldest, the head of the list
                    }
                    else
                    {
                        _nIgnoringNext--;
                    }
                }

                if (use)
                {
                    // this reading has been used
                    if (!readingsUsed.Contains(ReadingSource))
                        readingsUsed.Add(ReadingSource);
                }
            }
            public clsBarItem PostDrawItem
            {
                get { return _postDrawItem; }
                set { _postDrawItem = value; }
            }
            public BarStyle Style
            {
                get { return _style; }
                set { _style = value; }
            }
            public override bool ShowHistory
            {
                get { return _showHistory; }
                set { _showHistory = value; }
            }
            public bool ShowValue
            {
                get { return _showValue; }
                set { _showValue = value; }
            }
            public bool ShowPeakValue
            {
                get { return _showPeakValue; }
                set { _showPeakValue = value; }
            }
            public int HistoryDuration
            {
                get { return _msHistoryDuration; }
                set
                {
                    _msHistoryDuration = value;
                    if (_msHistoryDuration < UpdateInterval) _msHistoryDuration = UpdateInterval;
                }
            }
            public int IgnoreHistoryDuration
            {
                get { return _msIgnoreHistoryDuration; }
                set { _msIgnoreHistoryDuration = value; }
            }
            public override float MinHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Min();
                    }
                }
            }
            public override float MaxHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Max();
                    }
                }
            }
            public override void ClearHistory()
            {
                lock (_historyLock)
                {
                    _history.Clear();

                    _nIgnoringNext = _msIgnoreHistoryDuration / UpdateInterval;  // ignore next N readings to make up 2 second of ignore
                }
            }
            public override void History(out float minHistory, out float maxHistory)
            {
                if (_history.Count == 0)
                {
                    minHistory = Value;
                    maxHistory = Value;
                }
                else
                {
                    minHistory = _history.Min();
                    maxHistory = _history.Max();
                }
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set { _colour = value; }
            }
            public System.Drawing.Color ColourHigh
            {
                // used for segments and solid bar
                get { return _colourHigh; }
                set { _colourHigh = value; }
            }
            public System.Drawing.Color MarkerColour
            {
                get { return _markerColour; }
                set { _markerColour = value; }
            }
            public System.Drawing.Color PeakHoldMarkerColour
            {
                get { return _peakHoldMarkerColour; }
                set { _peakHoldMarkerColour = value; }
            }
            public System.Drawing.Color PeakValueColour
            {
                get { return _peakValueColour; }
                set { _peakValueColour = value; }
            }
            public bool PeakHold
            {
                get { return _peakHold; }
                set { _peakHold = value; }
            }
            public bool ShowMarker
            {
                get { return _showMarker; }
                set { _showMarker = value; }
            }
            public float StrokeWidth
            {
                get { return _strokeWidth; }
                set { _strokeWidth = value; }
            }
            public override Dictionary<float, PointF> ScaleCalibration
            {
                get { return _scaleCalibration; }
                set { }
            }

            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle FntStyle
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color FontColour
            {
                get { return _fontColor; }
                set { _fontColor = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }

            public override void HandleIncrement()
            {
                int n = (int)_units;
                n++;

                if (ReadingSource != Reading.ESTIMATED_PBSNR)
                {
                    if (n > (int)Units.U_V) n = (int)Units.DBM;
                }
                else
                {
                    if (n > (int)Units.S_UNTS) n = (int)Units.DBM;
                }

                _units = (Units)n;

                Debug.Print("Units = " + _units.ToString());
            }
            public override void HandleDecrement()
            {
                int n = (int)_units;
                n--;
                if (ReadingSource != Reading.ESTIMATED_PBSNR)
                {
                    if (n < (int)Units.DBM) n = (int)Units.U_V;
                }
                else
                {
                    if (n < (int)Units.DBM) n = (int)Units.S_UNTS;
                }

                _units = (Units)n;

                Debug.Print("Units = " + _units.ToString());
            }
            public Units Unit
            {
                get { return _units; }
                set
                {
                    int n = (int)value;
                    if (n < (int)Units.DBM || n > (int)Units.U_V) value = Units.DBM;
                    _units = value;
                }
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                if (_scaleCalibration != null || _scaleCalibration.Count > 0)
                {
                    float value = _scaleCalibration.OrderBy(p => p.Key).First().Key;
                    ZeroReading(out value, rx, ReadingSource);

                    if (!values.ContainsKey(ReadingSource))
                        values.Add(ReadingSource, value);
                    else
                        values[ReadingSource] = value;

                    return true;
                }
                return false;
                //    //switch (ReadingSource)
                //    //{
                //    //    case Reading.SIGNAL_STRENGTH:
                //    //    case Reading.AVG_SIGNAL_STRENGTH:
                //    //        {
                //    //            if (IsAboveS9Frequency(rx))
                //    //                value = -153; //S0
                //    //            else
                //    //                value = -133; //S0
                //    //        }
                //    //        break;
                //    //    case Reading.ADC_PK:
                //    //    case Reading.ADC_AV:
                //    //        {
                //    //            value = -120.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.AGC_AV:
                //    //    case Reading.AGC_PK:
                //    //        value = -125.0f;
                //    //        break;
                //    //    case Reading.AGC_GAIN:
                //    //        {
                //    //            value = -50.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.MIC:
                //    //    case Reading.MIC_PK:
                //    //        {
                //    //            value = -120.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.LEVELER:
                //    //    case Reading.LEVELER_PK:
                //    //        {
                //    //            value = -30.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.LVL_G:
                //    //        {
                //    //            value = 0f;
                //    //        }
                //    //        break;
                //    //    case Reading.ALC:
                //    //    case Reading.ALC_PK:
                //    //        {
                //    //            value = -120.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.ALC_G: //alc comp
                //    //        {
                //    //            value = 0f;
                //    //        }
                //    //        break;
                //    //    case Reading.ALC_GROUP:
                //    //        {
                //    //            value = -30.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.CFC_AV:
                //    //    case Reading.CFC_PK:
                //    //        {
                //    //            value = -30.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.CFC_G:
                //    //        {
                //    //            value = 0f;
                //    //        }
                //    //        break;
                //    //    case Reading.COMP:
                //    //    case Reading.COMP_PK:
                //    //        {
                //    //            value = -30.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.PWR:
                //    //    case Reading.REVERSE_PWR:
                //    //        {
                //    //            value = 0f;
                //    //        }
                //    //        break;
                //    //    case Reading.SWR:
                //    //        {
                //    //            value = 1.0f;
                //    //        }
                //    //        break;
                //    //    case Reading.ESTIMATED_PBSNR:
                //    //        {
                //    //            value = 0f;
                //    //        }
                //    //        break;
                //    //}                    
                //    return true;
                //}
                //value = 0;
                //return false;
            }
            public PointF HighPoint
            {
                get { return _highPoint; }
                set { _highPoint = value; }
            }
        }
        internal class clsSignalText : clsMeterItem
        {
            public enum Units
            {
                DBM = 0,
                S_UNTS,
                U_V
            }
            public enum BarStyle
            {
                None = 0,
                Line,
                SolidFilled,
                GradientFilled,
                Segments,
            }
            private List<float> _history;
            private int _msHistoryDuration; //ms
            private int _msIgnoreHistoryDuration;
            private bool _showValue;
            private bool _showPeakValue;
            private readonly object _historyLock = new object();
            private System.Drawing.Color _colour;
            private System.Drawing.Color _markerColour;
            private System.Drawing.Color _peakValueColour;
            private bool _peakHold;
            private bool _showMarker;
            private bool _showSubMarker;
            private Units _units;

            private string _fontFamily;
            private FontStyle _fontStyle;
            private System.Drawing.Color _fontColor;
            private float _fontSize;
            private int _nIgnoringNext;

            public clsSignalText()
            {
                _history = new List<float>();
                _msHistoryDuration = 2000;
                _msIgnoreHistoryDuration = 2000;
                _showValue = true;
                _showPeakValue = true;
                _nIgnoringNext = 0;

                _colour = System.Drawing.Color.Red;
                _markerColour = System.Drawing.Color.Yellow;
                _peakValueColour = System.Drawing.Color.Red;
                _peakHold = false;
                _showMarker = true;
                _showSubMarker = true;

                _units = Units.DBM;

                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _fontColor = System.Drawing.Color.DarkGray;
                _fontSize = 20f;

                ItemType = MeterItemType.SIGNAL_TEXT_DISPLAY;
                ReadingSource = Reading.SIGNAL_STRENGTH;
                UpdateInterval = 100;
                AttackRatio = 0.8f;
                DecayRatio = 0.2f;
                StoreSettings = false;
            }
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // get latest reading
                float reading = MeterManager.getReading(rx, ReadingSource);

                lock (_historyLock)
                {
                    if (reading > Value)
                        Value = (reading * AttackRatio) + (Value * (1f - AttackRatio));
                    else
                        Value = (reading * DecayRatio) + (Value * (1f - DecayRatio));

                    if (_nIgnoringNext <= 0)
                    {
                        // signal history
                        _history.Add(Value); // adds to end of the list
                        int numberToRemove = _history.Count - (_msHistoryDuration / UpdateInterval);
                        // the list is sized based on delay
                        if (numberToRemove > 0) _history.RemoveRange(0, numberToRemove); // remove the oldest, the head of the list
                    }
                    else
                    {
                        _nIgnoringNext--;
                    }
                }

                // this reading has been used
                if (!readingsUsed.Contains(ReadingSource))
                    readingsUsed.Add(ReadingSource);
            }
            public bool ShowValue
            {
                get { return _showValue; }
                set { _showValue = value; }
            }
            public bool ShowPeakValue
            {
                get { return _showPeakValue; }
                set { _showPeakValue = value; }
            }
            public int IgnoreHistoryDuration
            {
                get { return _msIgnoreHistoryDuration; }
                set { _msIgnoreHistoryDuration = value; }
            }
            public int HistoryDuration
            {
                get { return _msHistoryDuration; }
                set
                {
                    _msHistoryDuration = value;
                    if (_msHistoryDuration < UpdateInterval) _msHistoryDuration = UpdateInterval;
                }
            }
            public override float MinHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Min();
                    }
                }
            }
            public override float MaxHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Max();
                    }
                }
            }
            public override void ClearHistory()
            {
                lock (_historyLock)
                {
                    _history.Clear();

                    _nIgnoringNext = _msIgnoreHistoryDuration / UpdateInterval;  // ignore next N readings to make up 2 second of ignore
                }
            }
            public override void History(out float minHistory, out float maxHistory)
            {
                if (_history.Count == 0)
                {
                    minHistory = Value;
                    maxHistory = Value;
                }
                else
                {
                    minHistory = _history.Min();
                    maxHistory = _history.Max();
                }
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set { _colour = value; }
            }
            public System.Drawing.Color MarkerColour
            {
                get { return _markerColour; }
                set { _markerColour = value; }
            }
            public System.Drawing.Color PeakValueColour
            {
                get { return _peakValueColour; }
                set { _peakValueColour = value; }
            }
            public bool PeakHold
            {
                get { return _peakHold; }
                set { _peakHold = value; }
            }
            public bool ShowMarker
            {
                get { return _showMarker; }
                set { _showMarker = value; }
            }
            public bool ShowSubMarker
            {
                get { return _showSubMarker; }
                set { _showSubMarker = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle FntStyle
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color FontColour
            {
                get { return _fontColor; }
                set { _fontColor = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }

            public override void HandleIncrement()
            {
                int n = (int)_units;
                n++;

                if (ReadingSource != Reading.ESTIMATED_PBSNR)
                {
                    if (n > (int)Units.U_V) n = (int)Units.DBM;
                }
                else
                {
                    if (n > (int)Units.S_UNTS) n = (int)Units.DBM;
                }

                _units = (Units)n;
            }
            public override void HandleDecrement()
            {
                int n = (int)_units;
                n--;
                if (n < (int)Units.DBM) n = (int)Units.S_UNTS;

                _units = (Units)n;
            }
            public Units Unit
            {
                get { return _units; }
                set
                {
                    int n = (int)value;
                    if (n < (int)Units.DBM || n > (int)Units.U_V) value = Units.DBM;
                    _units = value;
                }
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                //switch (ReadingSource)
                //{
                //    case Reading.SIGNAL_STRENGTH:
                //    case Reading.AVG_SIGNAL_STRENGTH:
                //        {
                //            if (IsAboveS9Frequency(rx))
                //                value = -153; //S0
                //            else
                //                value = -133; //S0
                //            return true;
                //        }
                //        break;
                //}
                if (ReadingSource == Reading.SIGNAL_STRENGTH || ReadingSource == Reading.AVG_SIGNAL_STRENGTH)
                {
                    float value = 0;
                    ZeroReading(out value, rx, ReadingSource);

                    if (!values.ContainsKey(ReadingSource))
                        values.Add(ReadingSource, value);
                    else
                        values[ReadingSource] = value;

                    return true;
                }
                return false;
            }
        }
        internal class clsNeedleItem : clsMeterItem
        {
            public enum NeedlePlacement
            {
                Bottom = 0,
                Top,
                Left,
                Right
            }
            public enum NeedleStyle
            {
                Line = 0,
                Arrow,
                Hollow
            }
            public enum NeedleDirection
            {
                Clockwise = 0,
                CounterClockwise
            }
            private List<float> _history;
            private int _msHistoryDuration; //ms
            private int _msIgnoreHistoryDuration;
            private bool _showHistory;
            private readonly object _historyLock = new object();
            private NeedleStyle _style;
            private PointF _needleOffset;
            private NeedlePlacement _placement;
            private System.Drawing.Color _colour;
            private float _strokeWidth;
            private bool _scaleStrokeWidth;
            private NeedleDirection _needleDirection;
            private float _lengthFactor;
            private bool _setup;
            private Dictionary<float, PointF> _scaleCalibration;
            private PointF _radiusRatio;
            private bool _shadow;
            private bool _peakHold;
            private System.Drawing.Color _peakHoldMarkerColour;
            private int _peakNeedleFadeIn;
            private int _nIgnoringNext;

            public clsNeedleItem()
            {
                _history = new List<float>();
                _msHistoryDuration = 500;
                _msIgnoreHistoryDuration = 2000;
                _showHistory = false;
                _placement = NeedlePlacement.Bottom;
                _needleOffset.X = 0f;
                _needleOffset.Y = 0.5f;
                _lengthFactor = 1f;
                _setup = false;
                _radiusRatio.X = 1f;
                _radiusRatio.Y = 1f;
                _style = NeedleStyle.Line;
                _colour = System.Drawing.Color.White;
                _strokeWidth = 3f;
                _scaleStrokeWidth = false;
                _needleDirection = NeedleDirection.Clockwise;
                _scaleCalibration = new Dictionary<float, PointF>();
                _shadow = true;
                _peakHold = false;
                _peakHoldMarkerColour = System.Drawing.Color.Red;
                _nIgnoringNext = 0;

                ItemType = MeterItemType.NEEDLE;
                ReadingSource = Reading.NONE;
                UpdateInterval = 100;
                AttackRatio = 0.8f;
                DecayRatio = 0.2f;
                StoreSettings = false;
                _peakNeedleFadeIn = 0;
            }           
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // get latest reading
                //float reading = MeterManager.getReading(rx, ReadingSource);
                bool use;
                float reading;
                if (MMIOGuid == Guid.Empty)
                {
                    reading = MeterManager.getReading(rx, ReadingSource);
                    use = true;
                }
                else
                {
                    reading = 0;
                    //if (MultiMeterIO.Data.ContainsKey(GetMMIOGuid(0)))
                    if (MultiMeterIO.Data.ContainsKey(MMIOGuid))
                    {
                        //MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[GetMMIOGuid(0)];
                        MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[MMIOGuid];

                        //object val = mmio.GetVariable(GetMMIOVariable(0));
                        object val = mmio.GetVariable(MMIOVariable);
                        if (val is int)
                        {
                            int intVal = (int)val;
                            reading = (float)intVal;
                        }
                        else if (val is float)
                        {
                            reading = (float)val;
                        }
                        else if (val is double)
                        {
                            double doubleVal = (double)val;
                            reading = (float)doubleVal;
                        }
                    }
                    use = false;
                }

                lock (_historyLock)
                {
                    if (reading > Value)
                       Value = (reading * AttackRatio) + (Value * (1f - AttackRatio));
                    else
                        Value = (reading * DecayRatio) + (Value * (1f - DecayRatio));

                    if (_nIgnoringNext <= 0)
                    {
                        // signal history
                        _history.Add(Value); // adds to end of the list
                        int numberToRemove = _history.Count - (_msHistoryDuration / UpdateInterval);
                        // the list is sized based on delay
                        if (numberToRemove > 0) _history.RemoveRange(0, numberToRemove); // remove the oldest, the head of the list
                    }
                    else
                    {
                        _nIgnoringNext--;
                    }
                }

                if (use)
                {
                    // this reading has been used
                    if (!readingsUsed.Contains(ReadingSource))
                        readingsUsed.Add(ReadingSource);
                }
            }
            public float StrokeWidth
            {
                get { return _strokeWidth; }
                set { _strokeWidth = value; }
            }
            public bool ScaleStrokeWidth
            {
                get { return _scaleStrokeWidth; }
                set { _scaleStrokeWidth = value; }
            }
            public System.Drawing.Color Colour
            {
                get { return _colour; }
                set
                { _colour = value; }
            }
            public bool Setup
            {
                get { return _setup; }
                set { _setup = value; }
            }
            public NeedlePlacement Placement
            {
                get { return _placement; }
                set { _placement = value; }
            }
            public NeedleDirection Direction
            {
                get { return _needleDirection; }
                set { _needleDirection = value; }
            }
            public PointF NeedleOffset
            {
                get { return _needleOffset; }
                set { _needleOffset = value; }
            }
            public NeedleStyle Style
            {
                get { return _style; }
                set { _style = value; }
            }
            public System.Drawing.Color PeakHoldMarkerColour
            {
                get { return _peakHoldMarkerColour; }
                set { _peakHoldMarkerColour = value; }
            }
            public bool PeakHold
            {
                get { return _peakHold; }
                set { _peakHold = value; }
            }
            public override bool ShowHistory
            {
                get { return _showHistory; }
                set { _showHistory = value; }
            }
            public int IgnoreHistoryDuration
            {
                get { return _msIgnoreHistoryDuration; }
                set { _msIgnoreHistoryDuration = value; }
            }
            public int HistoryDuration
            {
                get { return _msHistoryDuration; }
                set
                {
                    _msHistoryDuration = value;
                    if (_msHistoryDuration < UpdateInterval) _msHistoryDuration = UpdateInterval;
                }
            }
            public override float MinHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if(_history.Count == 0) return Value;
                        return _history.Min();
                    }
                }
            }
            public override float MaxHistory
            {
                get
                {
                    lock (_historyLock)
                    {
                        if (_history.Count == 0) return Value;
                        return _history.Max();
                    }
                }
            }
            public override void ClearHistory()
            {
                lock (_historyLock)
                {
                    _history.Clear();

                    _nIgnoringNext = _msIgnoreHistoryDuration / UpdateInterval;  // ignore next N readings to make up 2 second of ignore
                }
            }
            public override void History(out float minHistory, out float maxHistory)
            {
                lock (_historyLock)
                {
                    if (_history.Count == 0)
                    {
                        minHistory = Value;
                        maxHistory = Value;
                    }
                    else
                    {
                        minHistory = _history.Min();
                        maxHistory = _history.Max();
                    }
                }
            }
            public float LengthFactor
            {
                get { return _lengthFactor; }
                set { _lengthFactor = value; }
            }
            public override Dictionary<float, PointF> ScaleCalibration
            {
                get { return _scaleCalibration; }
                set { }
            }
            public PointF RadiusRatio
            {
                get { return _radiusRatio; }
                set { _radiusRatio = value; }
            }
            public bool Shadow
            {
                get { return _shadow; }
                set { _shadow = value; }
            }
            public int PeakNeedleShadowFade
            {
                get { return _peakNeedleFadeIn; }
                set {
                    _peakNeedleFadeIn = value;
                    if (_peakNeedleFadeIn > 12) _peakNeedleFadeIn = 12;
                    if (_peakNeedleFadeIn < 0) _peakNeedleFadeIn = 0;
                }
            }
            public override bool ZeroOut(ref Dictionary<Reading, float> values, int rx)
            {
                if (_scaleCalibration != null || _scaleCalibration.Count > 0)
                {
                    float value = _scaleCalibration.OrderBy(p => p.Key).First().Key;
                    ZeroReading(out value, rx, ReadingSource);

                    if (!values.ContainsKey(ReadingSource))
                        values.Add(ReadingSource, value);
                    else
                        values[ReadingSource] = value;

                    return true;
                }
                return false;

                    //switch (ReadingSource)
                    //{
                    //    case Reading.SIGNAL_STRENGTH:
                    //    case Reading.AVG_SIGNAL_STRENGTH:
                    //        {
                    //            if (IsAboveS9Frequency(rx))
                    //                value = -153; //S0
                    //            else
                    //                value = -133; //S0

                    //        }
                    //        break;
                    //    case Reading.PWR:
                    //    case Reading.REVERSE_PWR:
                    //        {
                    //            value = 0f;
                    //        }
                    //        break;
                    //    case Reading.VOLTS:
                    //        {
                    //            value = 10f;
                    //        }
                    //        break;
                    //    case Reading.AMPS:
                    //        {
                    //            value = 10f;
                    //        }
                    //        break;
                    //    case Reading.SWR:
                    //        {
                    //            value = 1.0f;
                    //        }
                    //        break;
                    //    case Reading.ALC_G: //alc comp
                    //        {
                    //            value = 0f;
                    //        }
                    //        break;
                    //    case Reading.ALC_GROUP:
                    //        {
                    //            value = -30.0f;
                    //        }
                    //        break;
                    //}
                //    return true;
                //}
                //value = 0;
                //return false;
            }
        }
        internal class clsText : clsMeterItem
        {
            private string _sText;
            private string _fontFamily;
            private FontStyle _fontStyle;
            private System.Drawing.Color _color;
            private bool _fixedSize;
            private float _fontSize;
            public clsText()
            {
                _sText = "";
                _fontFamily = "Trebuchet MS";
                _fontStyle = FontStyle.Regular;
                _color = System.Drawing.Color.White;
                _fixedSize = false;
                _fontSize = 9f;
                _readingText = "";

                ItemType = MeterItemType.TEXT;
                ReadingSource = Reading.NONE;
                UpdateInterval = int.MaxValue; // very slow update
                StoreSettings = false;
            }            
            private void updateReadingText(float reading)
            {
                switch (_sText)
                {
                    case "%valueWithType%":
                        {
                            _readingText = reading.ToString("0.0") + MeterManager.ReadingUnits(ReadingSource);
                        }
                        break;
                    case "%value%":
                        {
                            _readingText = reading.ToString("0.0");
                        }
                        break;
                    default:
                        _readingText = "";
                        break;
                }
            }
            public override void Update(int rx, ref List<Reading> readingsUsed, Dictionary<Reading, object> all_list_item_readings = null)
            {
                // get latest reading
                float reading = MeterManager.getReading(rx, ReadingSource);

                updateReadingText(reading);

                // this reading has been used
                if (!readingsUsed.Contains(ReadingSource))
                    readingsUsed.Add(ReadingSource);
            }
            private string _readingText;
            public string Text
            {
                get
                {
                    string sText = _sText;
                    switch(_sText)
                    { 
                        case "%valueWithType%":
                        case "%value%":
                            sText = _readingText;
                        break; 
                    }

                    return sText; 
                }
                set { _sText = value; }
            }
            public string FontFamily
            {
                get { return _fontFamily; }
                set { _fontFamily = value; }
            }
            public FontStyle Style
            {
                get { return _fontStyle; }
                set { _fontStyle = value; }
            }
            public System.Drawing.Color Colour
            {
                get { return _color; }
                set { _color = value; }
            }
            public bool FixedSize
            {
                get { return _fixedSize; }
                set { _fixedSize = value; }
            }
            public float FontSize
            {
                get { return _fontSize; }
                set { _fontSize = value; }
            }
            public override float Value
            {
                get { return base.Value; }
                set { 
                    base.Value = value;
                    updateReadingText(value);
                }
            }
        }
        //internal class clsSpectrum : clsMeterItem
        //{
        //    private System.Drawing.Color _color;

        //    public clsSpectrum()
        //    {
        //        ItemType = MeterItemType.SPECTRUM;
        //        ReadingSource = Reading.NONE;
        //        UpdateInterval = 50;
        //        StoreSettings = false;
        //    }
        //}

        #endregion
        #region clsMeterItems
        public class clsMeter
        {
            // the single meter, that may contain many bars/labels/analog/images/etc
            private string _sId;
            private string _name;            
            private int _rx;
            private bool _enabled;
            private bool _show_on_rx;
            private bool _show_on_tx;

            private float _XRatio; // 0-1
            private float _YRatio; // 0-1

            private Dictionary<string, clsMeterItem> _meterItems;
            //private Dictionary<string, clsMeterItem> _sortedMeterItemsForZOrder;
            private List<clsMeterItem> _sortedMeterItemsForZOrder;
            private int _displayGroup;
            private List<string> _displayGroups;
            private bool _mox;
            private float _fPadX;
            private float _fPadY;
            private float _fHeight;

            private bool _split;
            private double _vfoA;
            private double _vfoB;
            private double _vfoSub;
            private bool _txVfoB;
            private DSPMode _modeVfoA;
            private DSPMode _modeVfoB;
            private Band _bandVfoA;
            private Band _bandVfoB;
            private Band _bandVfoASub;
            private Band _bandTX;
            private string _filterVfoAname;
            private string _filterVfoBname;
            private bool _rx2Enabled;
            private bool _multiRxEnabled;
            private DateTime _qso_start;
            private DateTime _qso_end;

            private int _tune_step_index;

            private bool _txeqEnabled;
            private bool _levelerEnabled;
            private bool _cfcEnabled;
            private bool _compandEnabled;

            private bool _quickSplitEnabled;

            private Filter _filterVfoA;
            private Filter _filterVfoB;

            private int _quickestRXUpdate;
            private int _quickestTXUpdate;

            private BandGroups _band_group;

            private string _vfoA_band_text;
            private string _vfoB_band_text;

            private bool _vfoA_lock;
            private bool _vfoB_lock;
            private bool _vfoA_sync;

            internal readonly object _meterItemsLock = new object();

            private void addMeterItem(clsMeterItem mi)
            {
                lock (_meterItemsLock)
                {
                    _meterItems.Add(mi.ID, mi);
                }
            }
            public string MeterVariablesReadingString(MeterType meter, int variable_index)
            {
                string ret = "";
                switch (meter)
                {
                    case MeterType.SIGNAL_STRENGTH: ret = Reading.SIGNAL_STRENGTH.ToString(); break;
                    case MeterType.AVG_SIGNAL_STRENGTH: ret = Reading.AVG_SIGNAL_STRENGTH.ToString(); break;
                    case MeterType.SIGNAL_TEXT: ret = Reading.SIGNAL_STRENGTH.ToString(); break;
                    case MeterType.ADC: ret = variable_index == 0 ? Reading.ADC_PK.ToString() : Reading.ADC_AV.ToString(); break;
                    case MeterType.AGC: ret = variable_index == 0 ? Reading.AGC_PK.ToString() : Reading.AGC_AV.ToString(); break;
                    case MeterType.AGC_GAIN: ret = Reading.AGC_GAIN.ToString(); break;
                    case MeterType.MIC: ret = variable_index == 0 ? Reading.MIC.ToString() : Reading.MIC_PK.ToString(); break;
                    case MeterType.PWR: ret = Reading.PWR.ToString(); break;
                    case MeterType.REVERSE_PWR: ret = Reading.REVERSE_PWR.ToString(); break;
                    case MeterType.ALC: ret = variable_index == 0 ? Reading.ALC.ToString() : Reading.ALC_PK.ToString(); break;
                    case MeterType.EQ: ret = variable_index == 0 ? Reading.EQ.ToString() : Reading.EQ_PK.ToString(); break;
                    case MeterType.LEVELER: ret = variable_index == 0 ? Reading.LEVELER.ToString() : Reading.LEVELER_PK.ToString(); break;
                    case MeterType.COMP: ret = variable_index == 0 ? Reading.COMP.ToString() : Reading.COMP_PK.ToString(); break;
                    //case MeterType.CPDR: ret = variable_index == 0 ? Reading.CPDR : Reading.CPDR_PK.ToString(); break; //CPDR is the same as comp
                    //case MeterType.CPDR: break.ToString(); break;
                    case MeterType.ALC_GAIN: ret = Reading.ALC_G.ToString(); break;
                    case MeterType.ALC_GROUP: ret = Reading.ALC_GROUP.ToString(); break;
                    case MeterType.LEVELER_GAIN: ret = Reading.LVL_G.ToString(); break;
                    case MeterType.CFC: ret = variable_index == 0 ? Reading.CFC_AV.ToString() : Reading.CFC_PK.ToString(); break;
                    case MeterType.CFC_GAIN: ret = Reading.CFC_G.ToString(); break;
                    case MeterType.MAGIC_EYE: ret = Reading.SIGNAL_STRENGTH.ToString(); break;
                    case MeterType.ESTIMATED_PBSNR: ret = Reading.ESTIMATED_PBSNR.ToString(); break;
                    //TODO !!!case MeterType.ANANMM: ret = 7.ToString(); break;
                    case MeterType.CROSS: ret = variable_index == 0 ? Reading.PWR.ToString() : Reading.REVERSE_PWR.ToString(); break;
                    case MeterType.SWR: ret = Reading.SWR.ToString(); break;
                    case MeterType.VFO_DISPLAY: ret = Reading.NONE.ToString(); break;
                    case MeterType.CLOCK: ret = Reading.NONE.ToString(); break;
                    case MeterType.SPACER: ret = Reading.NONE.ToString(); break;
                    case MeterType.TEXT_OVERLAY: ret = Reading.NONE.ToString(); break;
                    case MeterType.LED: ret = Reading.NONE.ToString(); break;
                    case MeterType.WEB_IMAGE: ret = Reading.NONE.ToString(); break;
                    case MeterType.BAND_BUTTONS: ret = Reading.NONE.ToString(); break;
                    case MeterType.MODE_BUTTONS: ret = Reading.NONE.ToString(); break;
                    case MeterType.FILTER_BUTTONS: ret = Reading.NONE.ToString(); break;
                    case MeterType.ANTENNA_BUTTONS: ret = Reading.NONE.ToString(); break;
                    case MeterType.DATA_OUT: ret = Reading.NONE.ToString(); break;
                    case MeterType.ROTATOR: ret = variable_index == 0 ? Reading.AZ.ToString() : Reading.ELE.ToString(); break;
                    case MeterType.HISTORY: ret = variable_index == 0 ? "Left Axis" : "Right Axis"; break;
                    case MeterType.TUNESTEP_BUTTONS: ret = Reading.NONE.ToString(); break;
                    case MeterType.DISCORD_BUTTONS: ret = Reading.NONE.ToString(); break;
                        //case MeterType.SPECTRUM: AddSpectrum(nDelay, 0, out bBottom, restoreIg).ToString(); break; break.ToString(); break;
                }
                return ret;
            }
            public int MeterVariables(MeterType meter)
            {
                switch (meter)
                {
                    case MeterType.SIGNAL_STRENGTH: return 1;
                    case MeterType.AVG_SIGNAL_STRENGTH: return 1;
                    case MeterType.SIGNAL_TEXT: return 1;
                    case MeterType.ADC: return 2;
                    case MeterType.AGC: return 2;
                    case MeterType.AGC_GAIN: return 1;
                    case MeterType.MIC: return 2;
                    case MeterType.PWR: return 1;
                    case MeterType.REVERSE_PWR: return 1;
                    case MeterType.ALC: return 2;
                    case MeterType.EQ: return 2;
                    case MeterType.LEVELER: return 2;
                    case MeterType.COMP: return 2;
                    //case MeterType.CPDR: break;
                    case MeterType.ALC_GAIN: return 1;
                    case MeterType.ALC_GROUP: return 1;
                    case MeterType.LEVELER_GAIN: return 1;
                    case MeterType.CFC: return 2;
                    case MeterType.CFC_GAIN: return 1;
                    case MeterType.MAGIC_EYE: return 1;
                    case MeterType.ESTIMATED_PBSNR: return 1;
                    case MeterType.ANANMM: return 7;
                    case MeterType.CROSS: return 2;
                    case MeterType.SWR: return 1;
                    case MeterType.VFO_DISPLAY: return 0;
                    case MeterType.CLOCK: return 0;
                    case MeterType.SPACER: return 0;
                    case MeterType.TEXT_OVERLAY: return 0;
                    case MeterType.LED: return 0;
                    case MeterType.WEB_IMAGE: return 0;
                    case MeterType.DATA_OUT: return 0;
                    case MeterType.ROTATOR: return 2;
                    case MeterType.BAND_BUTTONS: return 0;
                    case MeterType.MODE_BUTTONS: return 0;
                    case MeterType.FILTER_BUTTONS: return 0;
                    case MeterType.ANTENNA_BUTTONS: return 0;
                    case MeterType.HISTORY: return 2;
                    case MeterType.TUNESTEP_BUTTONS: return 0;
                    case MeterType.DISCORD_BUTTONS: return 0;
                        //case MeterType.SPECTRUM: AddSpectrum(nDelay, 0, out bBottom, restoreIg); break;
                }
                return 0;
            }
            public void AddMeter(MeterType meter, clsItemGroup restoreIg = null)
            {
                //restoreIg is passed in when used by restoreSettings, so that the item group can be ordered
                //correctly and the ID copied over
                float bBottom = 0;
                int nDelay = 50;

                switch (meter)
                {
                    case MeterType.SIGNAL_STRENGTH: AddSMeterBarSignal(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.AVG_SIGNAL_STRENGTH: AddSMeterBarSignalAvg(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.SIGNAL_TEXT: AddSMeterBarText(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.ADC: AddADCBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.AGC: AddAGCBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.AGC_GAIN: AddAGCGainBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.MIC: AddMicBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.PWR: AddPWRBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.REVERSE_PWR: AddREVPWRBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.ALC: AddALCBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.EQ: AddEQBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.LEVELER: AddLevelerBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.COMP: AddCompBar(nDelay, 0, out bBottom, restoreIg); break;
                    //case MeterType.CPDR: AddCompanderBar(nDelay, 0, out bBottom, restoreIg); break;//CPDR is the same as comp
                    case MeterType.ALC_GAIN: AddALCGainBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.ALC_GROUP: AddALCGroupBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.LEVELER_GAIN: AddLevelerGainBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.CFC: AddCFCBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.CFC_GAIN: AddCFCGainBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.MAGIC_EYE: AddMagicEye(nDelay, 0, out bBottom, 0.2f, restoreIg); break;
                    case MeterType.ESTIMATED_PBSNR: AddPBSNRBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.ANANMM: AddAnanMM(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.CROSS: AddCrossNeedle(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.SWR: AddSWRBar(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.VFO_DISPLAY: AddVFODisplay(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.CLOCK: AddClock(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.SPACER: AddSpacer(nDelay, 0, out bBottom, 0.1f, restoreIg); break;
                    case MeterType.TEXT_OVERLAY: AddTextOverlay(nDelay, 0, out bBottom, 0.1f, restoreIg); break;
                    case MeterType.LED: AddLed(nDelay, 0, out bBottom, 0.1f, restoreIg); break;
                    case MeterType.WEB_IMAGE: AddWebImage(nDelay, 0, out bBottom, 0.1f, restoreIg); break;
                    case MeterType.DATA_OUT: AddDataOut(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.ROTATOR: AddRotator(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.BAND_BUTTONS: AddBandButtons(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.MODE_BUTTONS: AddModeButtons(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.FILTER_BUTTONS: AddFilterButtons(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.ANTENNA_BUTTONS: AddAntennaButtons(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.HISTORY: AddHistory(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.TUNESTEP_BUTTONS: AddTunestepButtons(nDelay, 0, out bBottom, restoreIg); break;
                    case MeterType.DISCORD_BUTTONS: AddDiscordButtons(nDelay, 0, out bBottom, restoreIg); break;
                        //case MeterType.SPECTRUM: AddSpectrum(nDelay, 0, out bBottom, restoreIg); break;
                }

                // update the console data for the meter if we are not recovering
                if(restoreIg == null)
                    initConsoleData(this);

                // update state of items
                switch (meter)
                {
                    case MeterType.EQ:
                        OnEQChanged(TXEQEnabled, TXEQEnabled);
                        break;
                    case MeterType.LEVELER:
                    case MeterType.LEVELER_GAIN:
                        OnLevelerChanged(LevelerEnabled, LevelerEnabled);
                        break;
                    case MeterType.CFC:
                    case MeterType.CFC_GAIN:
                        OnCFCChanged(CFCEnabled, CFCEnabled);
                        break;
                    case MeterType.COMP:
                        OnCompandChanged(CompandEnabled, CompandEnabled);
                        break;
                }
            }
            #region meterDefs
            public string AddSMeterBarSignal(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            { 
                return addSMeterBar(nMSupdate, Reading.SIGNAL_STRENGTH, fTop, out fBottom, restoreIg);
            }
            public string AddSMeterBarSignalAvg(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                return addSMeterBar(nMSupdate, Reading.AVG_SIGNAL_STRENGTH, fTop, out fBottom, restoreIg);
            }
            private clsFadeCover getFadeCover(string sId)
            {
                System.Drawing.RectangleF boundRect = getBounds(sId);
                if(boundRect.IsEmpty) return null;

                clsFadeCover fc = new clsFadeCover();
                fc.TopLeft = new PointF(boundRect.X, boundRect.Y);
                fc.Size = new SizeF(boundRect.Width, boundRect.Height);
                fc.ParentID = sId;

                return fc;
            }
            private string addSMeterBar(int nMSupdate, Reading reading, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = reading;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.2f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 4000;
                cb.ShowHistory = true;
                cb.ShowValue = true;
                cb.Colour = System.Drawing.Color.CadetBlue;
                cb.ShowMarker = true;
                cb.PeakHoldMarkerColour = System.Drawing.Color.Red;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Red);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-133, new PointF(0, 0)); // position for S0 or below  // -133 is the edge, as S0 (-127) is the first small tick
                cb.ScaleCalibration.Add(-73, new PointF(0.5f, 0)); // position for S9
                cb.ScaleCalibration.Add(-13, new PointF(0.99f, 0)); // position for S9+60dB or above
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                if (IsAboveS9Frequency(_rx)) cb.Value -= 20;
                cb.ZOrder = 2;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 3;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc;
                sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                clsClickBox clb = new clsClickBox();
                clb.ParentID = ig.ID;
                clb.TopLeft = sc.TopLeft;
                clb.Size = sc.Size;
                clb.ChangesGroup = false;
                clb.PerformsIncDec = true;
                clb.RelatedMeterItem = cb;
                addMeterItem(clb);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                if (reading == Reading.SIGNAL_STRENGTH)
                    ig.MeterType = MeterType.SIGNAL_STRENGTH;
                else
                    ig.MeterType = MeterType.AVG_SIGNAL_STRENGTH;
                //lock (_meterItemLock)
                //{
                    ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;
                //}

                clsFadeCover fc = getFadeCover(ig.ID);
                if(fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return cb.ID;
            }            
            private string AddSMeterBarText(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;
                
                clsSolidColour sc;
                sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(_fPadX, fTop + _fPadY - _fHeight * 0.75f);
                sc.Size = new SizeF(1f - _fPadX * 2f, (_fHeight + _fHeight * 0.75f) * 1.5f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                clsSignalText cst = new clsSignalText();
                cst.ParentID = ig.ID;
                cst.Primary = true;
                cst.TopLeft = sc.TopLeft;
                cst.Size = sc.Size;
                cst.ReadingSource = Reading.AVG_SIGNAL_STRENGTH;
                cst.MMIOVariableIndex = 0;
                cst.AttackRatio = 0.8f;
                cst.DecayRatio = 0.2f;
                cst.UpdateInterval = nMSupdate;
                cst.HistoryDuration = 4000;
                cst.ShowHistory = false;
                cst.HistoryColour = System.Drawing.Color.Gray; // sub values uv/dbm
                cst.ShowValue = true;
                cst.ShowPeakValue = true;
                cst.FontColour = System.Drawing.Color.Yellow;
                cst.FontSize = 56f;
                cst.ZOrder = 2;
                cst.Value = -133; //S0
                addMeterItem(cst);

                //clsClickBox clb = new clsClickBox();
                //clb.ParentID = ig.ID;
                //clb.TopLeft = sc.TopLeft;
                //clb.Size = sc.Size;
                //clb.ChangesGroup = false;
                //clb.PerformsIncDec = true;
                //clb.RelatedMeterItem = cst;
                //addMeterItem(clb);

                fBottom = sc.TopLeft.Y + sc.Size.Height;

                ig.TopLeft = sc.TopLeft;
                ig.Size = new SizeF(sc.Size.Width, fBottom);
                ig.MeterType = MeterType.SIGNAL_TEXT;
                //lock (_meterItemLock)
                //{
                    ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;
                //}
                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cst.ID;
            }
            
            public string AddADCBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.ADC_PK;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.2f;
                cb.DecayRatio = 0.05f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 4000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Orange;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.CornflowerBlue);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-120, new PointF(0, 0));
                cb.ScaleCalibration.Add(-20, new PointF(0.8333f, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.99f, 0));
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 2;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                //avg on top
                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.TopLeft = cb.TopLeft;
                cb2.Size = cb.Size;
                cb2.ReadingSource = Reading.ADC_AV;
                cb2.MMIOVariableIndex = 1;
                cb2.ShowPeakValue = false;
                cb2.AttackRatio = 0.2f;
                cb2.DecayRatio = 0.05f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 0;
                cb2.ShowHistory = false;
                cb2.MarkerColour = System.Drawing.Color.DarkGray;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.CornflowerBlue);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-120, new PointF(0, 0));
                cb2.ScaleCalibration.Add(-20, new PointF(0.8333f, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.99f, 0));
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.ShowValue = false;
                cb2.ZOrder = 3;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb2.PostDrawItem = cb;
                addMeterItem(cb2);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.ADC;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddPBSNRBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.ESTIMATED_PBSNR;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.2f;
                cb.DecayRatio = 0.05f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 4000;
                cb.ShowHistory = false;
                cb.PeakHold = true;
                cb.Colour = System.Drawing.Color.DarkCyan;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Violet);
                cb.Style = clsBarItem.BarStyle.Segments;
                cb.ScaleCalibration.Add(0, new PointF(0, 0));
                cb.ScaleCalibration.Add(50, new PointF(0.8333f, 0));
                cb.ScaleCalibration.Add(60, new PointF(0.99f, 0));
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 2;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 3;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                clsClickBox clb = new clsClickBox();
                clb.ParentID = ig.ID;
                clb.TopLeft = sc.TopLeft;
                clb.Size = sc.Size;
                clb.ChangesGroup = false;
                clb.PerformsIncDec = true;
                clb.RelatedMeterItem = cb;
                addMeterItem(clb);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.ESTIMATED_PBSNR;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddAGCGainBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.AGC_GAIN;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.2f;
                cb.DecayRatio = 0.05f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 4000;
                cb.ShowHistory = true;
                cb.PeakHold = false;
                cb.Colour = System.Drawing.Color.DarkCyan;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Violet);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-50, new PointF(0, 0));
                cb.ScaleCalibration.Add(100, new PointF(0.857f, 0));
                cb.ScaleCalibration.Add(125, new PointF(0.99f, 0));
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 2;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 3;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.AGC_GAIN;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddAGCBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.AGC_PK;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.2f;
                cb.DecayRatio = 0.05f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 4000;
                cb.ShowHistory = true;
                cb.PeakHold = false;
                cb.Colour = System.Drawing.Color.DarkCyan;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Violet);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-125, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.5f, 0));
                cb.ScaleCalibration.Add(125, new PointF(0.99f, 0));
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 2;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.TopLeft = cb.TopLeft;
                cb2.Size = cb.Size;
                cb2.ReadingSource = Reading.AGC_AV;
                cb2.MMIOVariableIndex = 1;
                cb2.ShowPeakValue = false;
                cb2.AttackRatio = 0.2f;
                cb2.DecayRatio = 0.05f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 0;
                cb2.ShowHistory = false;
                cb2.PeakHold = false;
                cb2.Colour = System.Drawing.Color.DarkCyan;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Violet);
                cb2.MarkerColour = System.Drawing.Color.DarkGray;
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-125, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.5f, 0));
                cb2.ScaleCalibration.Add(125, new PointF(0.99f, 0));
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.ShowValue = false;
                cb2.ZOrder = 3;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb2.PostDrawItem = cb;
                addMeterItem(cb2);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.AGC;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddRotator(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                float fSize = 1f;

                //az
                clsRotatorItem ri = new clsRotatorItem();
                if (ri.ViewMode == clsRotatorItem.RotatorMode.BOTH)// use constructor value
                    fSize = 0.5f;
                else
                    fSize = 1f;

                //az
                ri.Padding = fSize;
                ri.ParentID = ig.ID;
                ri.Primary = true;
                ri.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                ri.Size = new SizeF(1f, fSize);
                ri.ZOrder = 4;
                ri.MMIOVariableIndex = 0;
                ri.ReadingSource = Reading.AZ;
                ri.UpdateInterval = 100;
                addMeterItem(ri);                                   

                //ele
                clsRotatorItem ri2 = new clsRotatorItem();
                ri2.Padding = fSize;
                ri2.ParentID = ig.ID;
                ri2.Primary = false;
                ri2.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                ri2.Size = new SizeF(1f, fSize);
                ri2.ZOrder = 4;
                ri2.MMIOVariableIndex = 1;
                ri2.ReadingSource = Reading.ELE;
                ri2.UpdateInterval = 100;
                addMeterItem(ri2);

                //grid
                clsImage img = new clsImage();
                img.ParentID = ig.ID;
                //img.TopLeft = ri.TopLeft;
                //img.Size = ri.Size;
                if (ri.ViewMode == clsRotatorItem.RotatorMode.BOTH)
                {
                    //img.TopLeft = new PointF(0.5f - (fSize / 2f), _fPadY - (_fHeight * 0.75f)/* + ((fSize - fSize) * 0.5f)*/);
                    //img.Size = new SizeF(fSize, fSize);
                    img.TopLeft = new PointF(0, _fPadY - (_fHeight * 0.75f) /*+ ((0.5f - 0.5f) * 0.5f)*/);
                    img.Size = new SizeF(1f, 0.5f);
                }
                else
                {
                    img.TopLeft = new PointF(0.5f - (fSize / 2f), _fPadY - (_fHeight * 0.75f)/* + ((fSize - fSize) * 0.5f)*/);
                    img.Size = new SizeF(fSize, fSize);
                }
                //
                img.ZOrder = 3;
                img.ImageName = ri.ImageName;
                addMeterItem(img);

                //map
                img = new clsImage();
                img.ParentID = ig.ID;
                PointF centre_tmp;
                if (ri.ViewMode == clsRotatorItem.RotatorMode.BOTH)
                {
                    centre_tmp = new PointF(0.085f + (0.38f / 2f), _fPadY - (_fHeight * 0.75f) + 0.06f + (0.38f / 2f)); // precalc from existing setup
                    img.Size = new SizeF(0.405f, 0.405f);                    
                }
                else
                {
                    centre_tmp = new PointF(0.5f - (fSize / 2f) + (0.12f * fSize) + (fSize * 0.76f / 2f), _fPadY - (_fHeight * 0.75f) + (0.12f * fSize) + (fSize * 0.76f / 2f)); // precalc from existing setup
                    img.Size = new SizeF(0.81f, 0.81f);
                }
                img.TopLeft = new PointF(centre_tmp.X - (img.Size.Width / 2f), centre_tmp.Y - (img.Size.Height / 2f));
                img.ImageName = ri.MapName;
                img.ZOrder = 2;
                img.ClippedEllipse = true;
                img.ClipEllipseCentre = new PointF(0.5f, 0.5f);
                img.ClipEllipseRadius = new SizeF(0.47f, 0.47f);
                addMeterItem(img);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = ri.TopLeft;
                sc.Size = ri.Size;
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = ri.TopLeft.Y + ri.Size.Height;

                ig.TopLeft = ri.TopLeft;
                ig.Size = new SizeF(ri.Size.Width, fBottom);
                ig.MeterType = MeterType.ROTATOR;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return ri.ID;
            }
            public string AddMagicEye(int nMSupdate, float fTop, out float fBottom, float fSize,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsMagicEyeItem me = new clsMagicEyeItem();
                me.ParentID = ig.ID;
                me.Primary = true;
                me.TopLeft = new PointF(0.5f - (fSize / 2f), fTop + _fPadY - (_fHeight * 0.75f));
                me.Size = new SizeF(fSize, fSize);
                me.ZOrder = 2;
                me.MMIOVariableIndex = 0;
                me.AttackRatio = 0.2f;
                me.DecayRatio = 0.05f;
                me.UpdateInterval = nMSupdate;
                me.Colour = System.Drawing.Color.Lime;
                me.ReadingSource = Reading.AVG_SIGNAL_STRENGTH;
                me.ScaleCalibration.Add(-127f, new PointF(0, 0));
                me.ScaleCalibration.Add(-73f, new PointF(0.85f, 0));
                me.ScaleCalibration.Add(-13f, new PointF(1f, 0));
                me.Value = me.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                if (IsAboveS9Frequency(_rx)) me.Value -= 20;
                addMeterItem(me);

                clsImage img = new clsImage();
                img.ParentID = ig.ID;
                img.TopLeft = me.TopLeft;
                img.Size = me.Size;
                img.ZOrder = 3;
                img.ImageName = "eye-bezel";
                addMeterItem(img);

                fBottom = me.TopLeft.Y + me.Size.Height;

                ig.TopLeft = me.TopLeft;
                ig.Size = new SizeF(me.Size.Width, fBottom);
                ig.MeterType = MeterType.MAGIC_EYE;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return me.ID;
            }
            public string AddSpacer(int nMSupdate, float fTop, out float fBottom, float fSize, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsSpacerItem me = new clsSpacerItem();
                me.ParentID = ig.ID;
                me.Primary = true;
                me.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                me.Size = new SizeF(1f, fSize);
                me.ZOrder = 2;
                addMeterItem(me);

                fBottom = me.TopLeft.Y + me.Size.Height;

                ig.TopLeft = me.TopLeft;
                ig.Size = new SizeF(me.Size.Width, fBottom);
                ig.MeterType = MeterType.SPACER;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return me.ID;
            }

            public string AddWebImage(int nMSupdate, float fTop, out float fBottom, float fSize, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsWebImage me = new clsWebImage(this, ig);
                me.ParentID = ig.ID;
                me.Primary = true;
                me.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                me.Size = new SizeF(1f, fSize);
                me.ZOrder = 2;
                addMeterItem(me);

                fBottom = me.TopLeft.Y + me.Size.Height;

                ig.TopLeft = me.TopLeft;
                ig.Size = new SizeF(me.Size.Width, fBottom);
                ig.MeterType = MeterType.WEB_IMAGE;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return me.ID;
            }
            public string AddDataOut(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsDataOut me = new clsDataOut();
                me.ParentID = ig.ID;
                me.Primary = true;
                me.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                me.Size = new SizeF(1f, 0);
                addMeterItem(me);

                fBottom = me.TopLeft.Y + me.Size.Height;

                ig.TopLeft = me.TopLeft;
                ig.Size = new SizeF(me.Size.Width, fBottom);
                ig.MeterType = MeterType.DATA_OUT;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                addMeterItem(ig);

                return me.ID;
            }
            public string AddTextOverlay(int nMSupdate, float fTop, out float fBottom, float fSize, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsTextOverlay me = new clsTextOverlay(this);
                me.ParentID = ig.ID;
                me.Primary = true;
                me.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                me.Size = new SizeF(1f, fSize);
                me.ZOrder = 999; // on top of everything
                addMeterItem(me);

                fBottom = me.TopLeft.Y + me.Size.Height;

                ig.TopLeft = me.TopLeft;
                ig.Size = new SizeF(me.Size.Width, fBottom);
                ig.MeterType = MeterType.TEXT_OVERLAY;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return me.ID;
            }
            public string AddLed(int nMSupdate, float fTop, out float fBottom, float fSize, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsLed me = new clsLed(this);
                me.ParentID = ig.ID;
                me.Primary = true;
                me.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                me.Size = new SizeF(1f, fSize);
                me.ZOrder = 998; // on top of everything, but under text
                addMeterItem(me);

                fBottom = me.TopLeft.Y + me.Size.Height;

                ig.TopLeft = me.TopLeft;
                ig.Size = new SizeF(me.Size.Width, fBottom);
                ig.MeterType = MeterType.LED;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return me.ID;
            }
            public string AddAnanMM(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                //anan multi meter
                clsNeedleItem ni = new clsNeedleItem();
                ni.ParentID = ig.ID;
                ni.Primary = true;
                ni.TopLeft = new PointF(0f, fTop + _fPadY - (_fHeight * 0.75f));
                ni.Size = new SizeF(1f, 0.441f); // image x to y ratio

                ni.OnlyWhenRX = true;
                ni.ReadingSource = Reading.AVG_SIGNAL_STRENGTH;
                ni.MMIOVariableIndex = 0;
                ni.AttackRatio = 0.8f;//0.1f;
                ni.DecayRatio = 0.2f;// 0.05f;
                ni.UpdateInterval = nMSupdate;
                ni.HistoryDuration = 4000;
                ni.ShowHistory = true;
                ni.Style = clsNeedleItem.NeedleStyle.Line;
                ni.Colour = System.Drawing.Color.FromArgb(255, 233, 51, 50);
                ni.HistoryColour = System.Drawing.Color.FromArgb(64, 233, 51, 50);
                ni.ZOrder = 4;
                ni.NeedleOffset = new PointF(0.004f, 0.736f);
                ni.RadiusRatio = new PointF(1f, 0.58f);
                ni.LengthFactor = 1.65f;
                ni.ScaleStrokeWidth = true;

                ni.ScaleCalibration.Add(-127f, new PointF(0.076f, 0.31f));
                ni.ScaleCalibration.Add(-121f, new PointF(0.131f, 0.272f));
                ni.ScaleCalibration.Add(-115f, new PointF(0.189f, 0.254f));
                ni.ScaleCalibration.Add(-109f, new PointF(0.233f, 0.211f));
                ni.ScaleCalibration.Add(-103f, new PointF(0.284f, 0.207f));
                ni.ScaleCalibration.Add(-97f, new PointF(0.326f, 0.177f));
                ni.ScaleCalibration.Add(-91f, new PointF(0.374f, 0.177f));
                ni.ScaleCalibration.Add(-85f, new PointF(0.414f, 0.151f));
                ni.ScaleCalibration.Add(-79f, new PointF(0.459f, 0.168f));
                ni.ScaleCalibration.Add(-73f, new PointF(0.501f, 0.142f));
                ni.ScaleCalibration.Add(-63f, new PointF(0.564f, 0.172f));
                ni.ScaleCalibration.Add(-53f, new PointF(0.63f, 0.164f));
                ni.ScaleCalibration.Add(-43f, new PointF(0.695f, 0.203f));
                ni.ScaleCalibration.Add(-33f, new PointF(0.769f, 0.211f));
                ni.ScaleCalibration.Add(-23f, new PointF(0.838f, 0.272f));
                ni.ScaleCalibration.Add(-13f, new PointF(0.926f, 0.31f));
                //ni.Value = -127f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni.Value = ni.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                if (IsAboveS9Frequency(_rx)) ni.Value -= 20;
                addMeterItem(ni);

                //volts
                clsNeedleItem ni2 = new clsNeedleItem();
                ni2.ParentID = ig.ID;
                ni2.TopLeft = ni.TopLeft;
                ni2.Size = ni.Size;
                ni2.ReadingSource = Reading.VOLTS;
                ni2.MMIOVariableIndex = 1;
                ni2.AttackRatio = 0.2f;
                ni2.DecayRatio = 0.2f;
                ni2.UpdateInterval = nMSupdate;
                ni2.HistoryDuration = 500;
                ni2.ShowHistory = false;
                ni2.Style = clsNeedleItem.NeedleStyle.Line;
                ni2.Colour = System.Drawing.Color.Black;
                ni2.ZOrder = 3;
                ni2.NeedleOffset = new PointF(0.004f, 0.736f);
                ni2.RadiusRatio = new PointF(1f, 0.58f);
                ni2.LengthFactor = 0.75f;
                ni2.ScaleStrokeWidth = true;
                ni2.ScaleCalibration.Add(10f, new PointF(0.559f, 0.756f));
                ni2.ScaleCalibration.Add(12.5f, new PointF(0.605f, 0.772f));
                ni2.ScaleCalibration.Add(15f, new PointF(0.665f, 0.784f));
                ni2.Value = 0f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni2.Value = ni2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni2);

                //amps
                clsNeedleItem ni3 = new clsNeedleItem(); ;
                ni3.ParentID = ig.ID;
                ni3.TopLeft = ni.TopLeft;
                ni3.Size = ni.Size;
                ni3.OnlyWhenTX = true;
                ni3.ReadingSource = Reading.AMPS;
                ni3.MMIOVariableIndex = 2;
                ni3.AttackRatio = 0.2f;
                ni3.DecayRatio = 0.2f;
                ni3.UpdateInterval = nMSupdate;
                ni3.HistoryDuration = 500;
                ni3.ShowHistory = false;
                ni3.Style = clsNeedleItem.NeedleStyle.Line;
                ni3.Colour = System.Drawing.Color.Black;
                ni3.ZOrder = 3;
                ni3.NeedleOffset = new PointF(0.004f, 0.736f);
                ni3.RadiusRatio = new PointF(1f, 0.58f);
                ni3.LengthFactor = 1.15f;
                ni3.ScaleStrokeWidth = true;
                ni3.DisplayGroup = 4;
                ni3.ScaleCalibration.Add(0f, new PointF(0.199f, 0.576f));
                ni3.ScaleCalibration.Add(2f, new PointF(0.27f, 0.54f));
                ni3.ScaleCalibration.Add(4f, new PointF(0.333f, 0.516f));
                ni3.ScaleCalibration.Add(6f, new PointF(0.393f, 0.504f));
                ni3.ScaleCalibration.Add(8f, new PointF(0.448f, 0.492f));
                ni3.ScaleCalibration.Add(10f, new PointF(0.499f, 0.492f));
                ni3.ScaleCalibration.Add(12f, new PointF(0.554f, 0.488f));
                ni3.ScaleCalibration.Add(14f, new PointF(0.608f, 0.5f));
                ni3.ScaleCalibration.Add(16f, new PointF(0.667f, 0.516f));
                ni3.ScaleCalibration.Add(18f, new PointF(0.728f, 0.54f));
                ni3.ScaleCalibration.Add(20f, new PointF(0.799f, 0.576f));
                ni3.Value = 0f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni3.Value = ni3.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni3);

                //
                clsClickBox clb = new clsClickBox();
                clb.ParentID = ig.ID;
                clb.TopLeft = new PointF(ni.TopLeft.X + 0.76f, ni.TopLeft.Y + 0.46f); // tl;
                clb.Size = new SizeF(0.18f, 0.044f);
                clb.OnlyWhenTX = true;
                clb.Button = MouseButtons.Left;
                addMeterItem(clb);

                //clsSolidColour sc = new clsSolidColour();
                //sc.ParentID = ig.ID;
                //sc.TopLeft = clb.TopLeft;
                //sc.Size = clb.Size;
                //sc.OnlyWhenTX = true;
                //sc.ZOrder = 6;
                //sc.Colour = System.Drawing.Color.FromArgb(48, System.Drawing.Color.White);
                //addMeterItem(sc);

                clsText tx = new clsText();
                tx.ParentID = ig.ID;
                tx.TopLeft = new PointF(clb.TopLeft.X + 0.02f, clb.TopLeft.Y + 0.005f);
                tx.Size = clb.Size;
                tx.OnlyWhenTX = true;
                tx.Text = "%group%";
                tx.ZOrder = 10;
                tx.Style = FontStyle.Bold;
                tx.Colour = System.Drawing.Color.DarkGray;
                tx.FixedSize = true;
                tx.FontSize = 8f;
                addMeterItem(tx);

                clsNeedleItem ni4 = new clsNeedleItem();
                ni4.ParentID = ig.ID;
                ni4.Primary = true;
                ni4.TopLeft = ni.TopLeft;
                ni4.Size = ni.Size;
                ni4.OnlyWhenTX = true;
                ni4.NormaliseTo100W = true;
                ni4.ReadingSource = Reading.PWR;
                ni4.MMIOVariableIndex = 3;
                ni4.AttackRatio = 0.2f;//0.325f;
                ni4.DecayRatio = 0.1f;//0.5f;
                ni4.UpdateInterval = nMSupdate;
                ni4.HistoryDuration = 4000;
                ni4.ShowHistory = true;
                ni4.Style = clsNeedleItem.NeedleStyle.Line;
                ni4.Colour = System.Drawing.Color.FromArgb(255, 233, 51, 50);
                ni4.HistoryColour = System.Drawing.Color.FromArgb(64, 233, 51, 50);
                ni4.ZOrder = 2;
                ni4.NeedleOffset = new PointF(0.004f, 0.736f);
                ni4.RadiusRatio = new PointF(1f, 0.58f);
                ni4.LengthFactor = 1.55f;
                ni4.ScaleStrokeWidth = true;
                ni4.DisplayGroup = 1;
                //ni.Setup = true;
                ni4.ScaleCalibration.Add(0f, new PointF(0.099f, 0.352f));
                ni4.ScaleCalibration.Add(5f, new PointF(0.164f, 0.312f));
                ni4.ScaleCalibration.Add(10f, new PointF(0.224f, 0.28f));
                ni4.ScaleCalibration.Add(25f, new PointF(0.335f, 0.236f));
                ni4.ScaleCalibration.Add(30f, new PointF(0.367f, 0.228f));
                ni4.ScaleCalibration.Add(40f, new PointF(0.436f, 0.22f));
                ni4.ScaleCalibration.Add(50f, new PointF(0.499f, 0.212f));
                ni4.ScaleCalibration.Add(60f, new PointF(0.559f, 0.216f));
                ni4.ScaleCalibration.Add(100f, new PointF(0.751f, 0.272f));
                ni4.ScaleCalibration.Add(150f, new PointF(0.899f, 0.352f));
                //ni4.Value = 0f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni4.Value = ni4.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni4);

                clsNeedleScalePwrItem nspi = new clsNeedleScalePwrItem();
                nspi.ParentID = ig.ID;
                nspi.TopLeft = ni.TopLeft;
                nspi.Size = ni.Size;
                nspi.Primary = true;
                nspi.Marks = 7;
                nspi.ReadingSource = Reading.PWR;
                nspi.NeedleOffset = new PointF(0.004f, 0.736f);
                nspi.RadiusRatio = new PointF(1f, 0.55f);
                nspi.LengthFactor = 1.51f;
                nspi.ZOrder = 2;
                nspi.LowColour = System.Drawing.Color.Gray;
                nspi.FntStyle = FontStyle.Bold;
                nspi.FontSize = 22;
                nspi.ScaleCalibration.Add(0f, new PointF(0.099f, 0.352f));
                nspi.ScaleCalibration.Add(5f, new PointF(0.164f, 0.312f));
                nspi.ScaleCalibration.Add(10f, new PointF(0.224f, 0.28f));
                nspi.ScaleCalibration.Add(25f, new PointF(0.335f, 0.236f));
                nspi.ScaleCalibration.Add(30f, new PointF(0.367f, 0.228f));
                nspi.ScaleCalibration.Add(40f, new PointF(0.436f, 0.22f));
                nspi.ScaleCalibration.Add(50f, new PointF(0.499f, 0.212f));
                nspi.ScaleCalibration.Add(60f, new PointF(0.559f, 0.216f));
                nspi.ScaleCalibration.Add(100f, new PointF(0.751f, 0.272f));
                nspi.ScaleCalibration.Add(150f, new PointF(0.899f, 0.352f));
                addMeterItem(nspi);

                clsNeedleItem ni5 = new clsNeedleItem();
                ni5.ParentID = ig.ID;
                ni5.TopLeft = ni.TopLeft;
                ni5.Size = ni.Size;
                ni5.OnlyWhenTX = true;
                ni5.ReadingSource = Reading.SWR;
                ni5.MMIOVariableIndex = 4;
                ni5.AttackRatio = 0.2f;//0.325f;
                ni5.DecayRatio = 0.1f;//0.5f;
                ni5.UpdateInterval = nMSupdate;
                ni5.HistoryDuration = 4000;
                ni5.ShowHistory = true;
                ni5.Style = clsNeedleItem.NeedleStyle.Line;
                ni5.Colour = System.Drawing.Color.Black;
                ni5.HistoryColour = System.Drawing.Color.FromArgb(64, System.Drawing.Color.CornflowerBlue);
                ni5.ZOrder = 3;
                ni5.NeedleOffset = new PointF(0.004f, 0.736f);
                ni5.RadiusRatio = new PointF(1f, 0.58f);
                ni5.LengthFactor = 1.36f;
                ni5.ScaleStrokeWidth = true;
                ni5.DisplayGroup = 1;
                ni5.ScaleCalibration.Add(1f, new PointF(0.152f, 0.468f));
                ni5.ScaleCalibration.Add(1.5f, new PointF(0.28f, 0.404f));
                ni5.ScaleCalibration.Add(2f, new PointF(0.393f, 0.372f));
                ni5.ScaleCalibration.Add(2.5f, new PointF(0.448f, 0.36f));
                ni5.ScaleCalibration.Add(3f, new PointF(0.499f, 0.36f));
                ni5.ScaleCalibration.Add(10f, new PointF(0.847f, 0.476f));
                //ni5.Value = 1f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni5.Value = ni5.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni5);

                clsNeedleItem ni6 = new clsNeedleItem();
                ni6.ParentID = ig.ID;
                ni6.TopLeft = ni.TopLeft;
                ni6.Size = ni.Size;
                ni6.OnlyWhenTX = true;
                ni6.ReadingSource = Reading.ALC_G; // alc_comp
                ni6.MMIOVariableIndex = 5;
                ni6.AttackRatio = 0.2f;//0.325f;
                ni6.DecayRatio = 0.1f;//0.5f;
                ni6.UpdateInterval = nMSupdate;
                ni6.HistoryDuration = 4000;
                ni6.ShowHistory = false;
                ni6.Style = clsNeedleItem.NeedleStyle.Line;
                ni6.Colour = System.Drawing.Color.Black;
                ni6.ZOrder = 3;
                ni6.NeedleOffset = new PointF(0.004f, 0.736f);
                ni6.RadiusRatio = new PointF(1f, 0.58f);
                ni6.LengthFactor = 0.96f;
                ni6.ScaleStrokeWidth = true;
                ni6.DisplayGroup = 2;
                ni6.ScaleCalibration.Add(0f, new PointF(0.249f, 0.68f));
                ni6.ScaleCalibration.Add(5f, new PointF(0.342f, 0.64f));
                ni6.ScaleCalibration.Add(10f, new PointF(0.425f, 0.624f));
                ni6.ScaleCalibration.Add(15f, new PointF(0.499f, 0.62f));
                ni6.ScaleCalibration.Add(20f, new PointF(0.571f, 0.628f));
                ni6.ScaleCalibration.Add(25f, new PointF(0.656f, 0.64f));
                ni6.ScaleCalibration.Add(30f, new PointF(0.751f, 0.688f));
                //ni6.Value = 0f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni6.Value = ni6.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni6);

                clsNeedleItem ni7 = new clsNeedleItem();
                ni7.ParentID = ig.ID;
                ni7.TopLeft = ni.TopLeft;
                ni7.Size = ni.Size;
                ni7.OnlyWhenTX = true;
                ni7.ReadingSource = Reading.ALC_GROUP;
                ni7.MMIOVariableIndex = 6;
                ni7.AttackRatio = 0.2f;//0.325f;
                ni7.DecayRatio = 0.1f;//0.5f;
                ni7.UpdateInterval = nMSupdate;
                ni7.HistoryDuration = 4000;
                ni7.ShowHistory = false;
                ni7.Style = clsNeedleItem.NeedleStyle.Line;
                ni7.Colour = System.Drawing.Color.Black;
                //ni.HistoryColour = System.Drawing.Color.FromArgb(64, 233, 51, 50);
                ni7.ZOrder = 3;
                ni7.NeedleOffset = new PointF(0.004f, 0.736f);
                ni7.RadiusRatio = new PointF(1f, 0.58f);
                ni7.LengthFactor = 0.75f;
                ni7.ScaleStrokeWidth = true;
                ni7.DisplayGroup = 3;
                //ni.Setup = true;
                ni7.ScaleCalibration.Add(-30f, new PointF(0.295f, 0.804f));
                ni7.ScaleCalibration.Add(0f, new PointF(0.332f, 0.784f));
                ni7.ScaleCalibration.Add(25f, new PointF(0.499f, 0.756f));
                //ni7.Value = -30f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni7.Value = ni7.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni7);
                //

                clsImage img = new clsImage();
                img.ParentID = ig.ID;
                img.Primary = true;
                img.TopLeft = ni.TopLeft;
                img.Size = ni.Size;
                img.ZOrder = 1;
                img.ImageName = "ananMM";
                addMeterItem(img);

                img = new clsImage();
                img.ParentID = ig.ID;
                img.OnlyWhenRX = true;
                img.TopLeft = new PointF(ni.TopLeft.X, ni.TopLeft.Y + ni.Size.Height);
                img.Size = new SizeF(1f, 75 / 900f);//0.101f); // image x to y ratio : 75 pixels y, 900 x
                img.ZOrder = 5;
                img.ImageName = "ananMM-bg";
                addMeterItem(img);

                img = new clsImage();
                img.ParentID = ig.ID;
                img.Primary = true;
                img.OnlyWhenTX = true;
                img.TopLeft = new PointF(ni.TopLeft.X, ni.TopLeft.Y + ni.Size.Height);
                img.Size = new SizeF(1f, 75 / 900f);//0.101f); // image x to y ratio : 75 pixels y, 900 x
                img.ZOrder = 5;
                img.ImageName = "ananMM-bg-tx";
                addMeterItem(img);

                fBottom = img.TopLeft.Y + img.Size.Height;

                ig.TopLeft = ni.TopLeft;
                ig.Size = new SizeF(ni.Size.Width, fBottom);
                ig.MeterType = MeterType.ANANMM;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                AddDisplayGroup("ALL"); //0
                AddDisplayGroup("PWR/SWR"); //1
                AddDisplayGroup("Comp"); //2
                AddDisplayGroup("ALC"); //3
                AddDisplayGroup("Volts/Amps"); //4
                DisplayGroup = 1;

                return ni.ID;
            }
            public string AddCrossNeedle(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsNeedleItem ni = new clsNeedleItem();
                ni.ParentID = ig.ID;
                ni.Primary = true;
                ni.TopLeft = new PointF(0f, fTop + _fPadY - (_fHeight * 0.75f));
                ni.Size = new SizeF(1f, 0.782f); // image x to y ratio
                ni.ReadingSource = Reading.PWR;
                ni.MMIOVariableIndex = 0;
                ni.AttackRatio = 0.2f;
                ni.DecayRatio = 0.1f;
                ni.UpdateInterval = nMSupdate;
                ni.HistoryDuration = 4000;
                ni.ShowHistory = true;
                ni.StrokeWidth = 2.5f;
                ni.Style = clsNeedleItem.NeedleStyle.Line;
                ni.Colour = System.Drawing.Color.Black;
                ni.HistoryColour = System.Drawing.Color.FromArgb(96, System.Drawing.Color.Red);
                ni.ZOrder = 4;
                ni.NeedleOffset = new PointF(0.322f, 0.611f);
                ni.LengthFactor = 1.62f;
                ni.ScaleStrokeWidth = true;
                ni.ScaleCalibration.Add(0f, new PointF(0.052f, 0.732f));
                ni.ScaleCalibration.Add(5f, new PointF(0.146f, 0.528f));
                ni.ScaleCalibration.Add(10f, new PointF(0.188f, 0.434f));
                ni.ScaleCalibration.Add(15f, new PointF(0.235f, 0.387f));
                ni.ScaleCalibration.Add(20f, new PointF(0.258f, 0.338f));
                ni.ScaleCalibration.Add(25f, new PointF(0.303f, 0.313f));
                ni.ScaleCalibration.Add(30f, new PointF(0.321f, 0.272f));
                ni.ScaleCalibration.Add(35f, new PointF(0.361f, 0.257f));
                ni.ScaleCalibration.Add(40f, new PointF(0.381f, 0.223f));
                ni.ScaleCalibration.Add(50f, new PointF(0.438f, 0.181f));
                ni.ScaleCalibration.Add(60f, new PointF(0.483f, 0.155f));
                ni.ScaleCalibration.Add(70f, new PointF(0.532f, 0.13f));
                ni.ScaleCalibration.Add(80f, new PointF(0.577f, 0.111f));
                ni.ScaleCalibration.Add(90f, new PointF(0.619f, 0.098f));
                ni.ScaleCalibration.Add(100f, new PointF(0.662f, 0.083f));
                ni.NormaliseTo100W = true;
                //ni.Value = 0f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni.Value = ni.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni);

                clsNeedleScalePwrItem nspi = new clsNeedleScalePwrItem();
                nspi.ParentID = ig.ID;
                nspi.TopLeft = ni.TopLeft;
                nspi.Size = ni.Size;
                nspi.Primary = true;
                nspi.Marks = 8;
                nspi.ReadingSource = Reading.PWR;
                nspi.NeedleOffset = new PointF(0.318f, 0.611f);
                nspi.LengthFactor = 1.685f;
                nspi.ZOrder = 5;
                nspi.LowColour = System.Drawing.Color.Gray;
                nspi.FntStyle = FontStyle.Bold;
                nspi.FontSize = 16;
                nspi.ScaleCalibration.Add(0f, new PointF(0.052f, 0.732f));
                nspi.ScaleCalibration.Add(5f, new PointF(0.146f, 0.528f));
                nspi.ScaleCalibration.Add(10f, new PointF(0.188f, 0.434f));
                nspi.ScaleCalibration.Add(15f, new PointF(0.235f, 0.387f));
                nspi.ScaleCalibration.Add(20f, new PointF(0.258f, 0.338f));
                nspi.ScaleCalibration.Add(25f, new PointF(0.303f, 0.313f));
                nspi.ScaleCalibration.Add(30f, new PointF(0.321f, 0.272f));
                nspi.ScaleCalibration.Add(35f, new PointF(0.361f, 0.257f));
                nspi.ScaleCalibration.Add(40f, new PointF(0.381f, 0.223f));
                nspi.ScaleCalibration.Add(50f, new PointF(0.438f, 0.181f));
                nspi.ScaleCalibration.Add(60f, new PointF(0.483f, 0.155f));
                nspi.ScaleCalibration.Add(70f, new PointF(0.532f, 0.13f));
                nspi.ScaleCalibration.Add(80f, new PointF(0.577f, 0.111f));
                nspi.ScaleCalibration.Add(90f, new PointF(0.619f, 0.098f));
                nspi.ScaleCalibration.Add(100f, new PointF(0.662f, 0.083f));
                addMeterItem(nspi);

                clsNeedleItem ni2 = new clsNeedleItem();
                ni2.ParentID = ig.ID;
                ni2.TopLeft = ni.TopLeft;
                ni2.Size = ni.Size;
                ni2.ReadingSource = Reading.REVERSE_PWR;
                ni2.MMIOVariableIndex = 1;
                ni2.AttackRatio = 0.2f;//0.325f;
                ni2.DecayRatio = 0.1f;//0.5f;
                ni2.UpdateInterval = nMSupdate;
                ni2.HistoryDuration = 4000;
                ni2.ShowHistory = true;
                ni2.StrokeWidth = 2.5f;
                ni2.Direction = clsNeedleItem.NeedleDirection.CounterClockwise;
                ni2.Style = clsNeedleItem.NeedleStyle.Line;
                ni2.Colour = System.Drawing.Color.Black;
                ni2.HistoryColour = System.Drawing.Color.FromArgb(96, System.Drawing.Color.CornflowerBlue);
                ni2.ZOrder = 3;
                ni2.NeedleOffset = new PointF(-0.322f, 0.611f);
                ni2.LengthFactor = 1.62f;
                ni2.ScaleStrokeWidth = true;
                ni2.ScaleCalibration.Add(0f, new PointF(0.948f, 0.74f));
                ni2.ScaleCalibration.Add(0.25f, new PointF(0.913f, 0.7f));
                ni2.ScaleCalibration.Add(0.5f, new PointF(0.899f, 0.638f));
                ni2.ScaleCalibration.Add(0.75f, new PointF(0.875f, 0.594f));
                ni2.ScaleCalibration.Add(1f, new PointF(0.854f, 0.538f));
                ni2.ScaleCalibration.Add(2f, new PointF(0.814f, 0.443f));
                ni2.ScaleCalibration.Add(3f, new PointF(0.769f, 0.4f));
                ni2.ScaleCalibration.Add(4f, new PointF(0.744f, 0.351f));
                ni2.ScaleCalibration.Add(5f, new PointF(0.702f, 0.321f));
                ni2.ScaleCalibration.Add(6f, new PointF(0.682f, 0.285f));
                ni2.ScaleCalibration.Add(7f, new PointF(0.646f, 0.268f));
                ni2.ScaleCalibration.Add(8f, new PointF(0.626f, 0.234f));
                ni2.ScaleCalibration.Add(9f, new PointF(0.596f, 0.228f));
                ni2.ScaleCalibration.Add(10f, new PointF(0.569f, 0.196f));
                ni2.ScaleCalibration.Add(12f, new PointF(0.524f, 0.166f));
                ni2.ScaleCalibration.Add(14f, new PointF(0.476f, 0.14f));
                ni2.ScaleCalibration.Add(16f, new PointF(0.431f, 0.121f));
                ni2.ScaleCalibration.Add(18f, new PointF(0.393f, 0.109f));
                ni2.ScaleCalibration.Add(20f, new PointF(0.349f, 0.098f));
                ni2.NormaliseTo100W = true;
                //ni2.Value = 0f;
                //MeterManager.setReading(rx, ni.ReadingSource, ni.Value);
                ni2.Value = ni2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                addMeterItem(ni2);

                clsNeedleScalePwrItem nspi2 = new clsNeedleScalePwrItem();
                nspi2.ParentID = ig.ID;
                nspi2.TopLeft = ni.TopLeft;
                nspi2.Size = ni.Size;
                nspi2.Marks = 8;
                nspi2.ReadingSource = Reading.PWR;
                nspi2.Direction = clsNeedleItem.NeedleDirection.CounterClockwise;
                nspi2.NeedleOffset = new PointF(-0.322f, 0.611f);
                nspi2.LengthFactor = 1.685f;
                nspi2.ZOrder = 5;
                nspi2.LowColour = System.Drawing.Color.Gray;
                nspi2.FntStyle = FontStyle.Bold;
                nspi2.FontSize = 16;
                nspi2.ScaleCalibration.Add(0f, new PointF(0.948f, 0.74f));
                nspi2.ScaleCalibration.Add(0.25f, new PointF(0.913f, 0.7f));
                nspi2.ScaleCalibration.Add(0.5f, new PointF(0.899f, 0.638f));
                nspi2.ScaleCalibration.Add(0.75f, new PointF(0.875f, 0.594f));
                nspi2.ScaleCalibration.Add(1f, new PointF(0.854f, 0.538f));
                nspi2.ScaleCalibration.Add(2f, new PointF(0.814f, 0.443f));
                nspi2.ScaleCalibration.Add(3f, new PointF(0.769f, 0.4f));
                nspi2.ScaleCalibration.Add(4f, new PointF(0.744f, 0.351f));
                nspi2.ScaleCalibration.Add(5f, new PointF(0.702f, 0.321f));
                nspi2.ScaleCalibration.Add(6f, new PointF(0.682f, 0.285f));
                nspi2.ScaleCalibration.Add(7f, new PointF(0.646f, 0.268f));
                nspi2.ScaleCalibration.Add(8f, new PointF(0.626f, 0.234f));
                nspi2.ScaleCalibration.Add(9f, new PointF(0.596f, 0.228f));
                nspi2.ScaleCalibration.Add(10f, new PointF(0.569f, 0.196f));
                nspi2.ScaleCalibration.Add(12f, new PointF(0.524f, 0.166f));
                nspi2.ScaleCalibration.Add(14f, new PointF(0.476f, 0.14f));
                nspi2.ScaleCalibration.Add(16f, new PointF(0.431f, 0.121f));
                nspi2.ScaleCalibration.Add(18f, new PointF(0.393f, 0.109f));
                nspi2.ScaleCalibration.Add(20f, new PointF(0.349f, 0.098f));
                addMeterItem(nspi2);

                clsImage img = new clsImage();
                img.ParentID = ig.ID;
                img.Primary = true;
                img.TopLeft = ni.TopLeft;
                img.Size = ni.Size;
                img.ZOrder = 1;
                img.ImageName = "cross-needle";
                addMeterItem(img);

                clsImage img2 = new clsImage();
                img2.ParentID = ig.ID;
                img2.TopLeft = new PointF(ni.TopLeft.X, ni.TopLeft.Y + ni.Size.Height - 0.180f); // it goes too high if we move it up by 0.217f
                img2.Size = new SizeF(1f, 0.217f);
                img2.ZOrder = 5;
                img2.ImageName = "cross-needle-bg";
                addMeterItem(img2);

                fBottom = img2.TopLeft.Y + img2.Size.Height;

                ig.TopLeft = ni.TopLeft;
                ig.Size = new SizeF(ni.Size.Width, fBottom);
                ig.MeterType = MeterType.CROSS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return ni.ID;
            }
            public string AddMicBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.Primary = true;
                cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb2.ReadingSource = Reading.MIC_PK;
                cb2.MMIOVariableIndex = 1;
                cb2.AttackRatio = 0.8f;
                cb2.DecayRatio = 0.1f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 2000;
                cb2.ShowHistory = true;
                cb2.MarkerColour = System.Drawing.Color.Yellow;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Red);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb2.ZOrder = 2;
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb2);

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.TopLeft = cb2.TopLeft;
                cb.Size = cb2.Size;
                cb.ReadingSource = Reading.MIC;
                cb.MMIOVariableIndex = 0;
                cb.ShowPeakValue = false;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = false;
                cb.MarkerColour = System.Drawing.Color.DarkGray;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Red);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb.ShowValue = false;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 3;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb.PostDrawItem = cb2;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.MIC;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddEQBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.Primary = true;
                cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb2.ReadingSource = Reading.EQ_PK;
                cb2.MMIOVariableIndex = 1;
                cb2.AttackRatio = 0.8f;
                cb2.DecayRatio = 0.1f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 2000;
                cb2.ShowHistory = true;
                cb2.MarkerColour = System.Drawing.Color.Yellow;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.CornflowerBlue);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb2.ZOrder = 2;
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.ShowValue = true;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;                
                addMeterItem(cb2);

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.TopLeft = cb2.TopLeft;
                cb.Size = cb2.Size;
                cb.ReadingSource = Reading.EQ;
                cb.MMIOVariableIndex = 0;
                cb.ShowPeakValue = false;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = false;
                cb.MarkerColour = System.Drawing.Color.DarkGray;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.CornflowerBlue);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb.ShowValue = false;
                cb.ZOrder = 3;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb.PostDrawItem = cb2;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.ParentID = ig.ID;
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.EQ;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddLevelerBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.Primary = true;
                cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb2.ReadingSource = Reading.LEVELER_PK;
                cb2.MMIOVariableIndex = 1;
                cb2.AttackRatio = 0.8f;
                cb2.DecayRatio = 0.1f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 2000;
                cb2.ShowHistory = true;
                cb2.MarkerColour = System.Drawing.Color.Yellow;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Purple);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb2.ZOrder = 2;
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb2);

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.TopLeft = cb2.TopLeft;
                cb.Size = cb2.Size;
                cb.ReadingSource = Reading.LEVELER;
                cb.MMIOVariableIndex = 0;
                cb.ShowPeakValue = false;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = false;
                cb.MarkerColour = System.Drawing.Color.DarkGray;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Purple);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb.ZOrder = 3;
                cb.ShowValue = false;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb.PostDrawItem = cb2;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.LEVELER;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddLevelerGainBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.LVL_G;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Yellow;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Purple);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(0, new PointF(0, 0));
                cb.ScaleCalibration.Add(20, new PointF(0.8f, 0));
                cb.ScaleCalibration.Add(25, new PointF(0.99f, 0));
                cb.ZOrder = 2;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.LEVELER_GAIN;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddALCBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.Primary = true;
                cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb2.ReadingSource = Reading.ALC_PK;
                cb2.MMIOVariableIndex = 1;
                cb2.AttackRatio = 0.8f;
                cb2.DecayRatio = 0.1f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 2000;
                cb2.ShowHistory = true;
                cb2.MarkerColour = System.Drawing.Color.Yellow;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.LemonChiffon);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb2.ZOrder = 2;
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb2);

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.TopLeft = cb2.TopLeft;
                cb.Size = cb2.Size;
                cb.ReadingSource = Reading.ALC;
                cb.MMIOVariableIndex = 0;
                cb.ShowPeakValue = false;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = false;
                cb.MarkerColour = System.Drawing.Color.DarkGray;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.LemonChiffon);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb.ZOrder = 3;
                cb.ShowValue = false;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb.PostDrawItem = cb2;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.ALC;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddALCGainBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.ALC_G;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Yellow;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.LemonChiffon);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(0, new PointF(0, 0));
                cb.ScaleCalibration.Add(20, new PointF(0.8f, 0));
                cb.ScaleCalibration.Add(25, new PointF(0.99f, 0));
                cb.ZOrder = 2;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.ALC_GAIN;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddALCGroupBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.ALC_GROUP;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Yellow;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.LemonChiffon);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.5f, 0));
                cb.ScaleCalibration.Add(25, new PointF(0.99f, 0));
                cb.ZOrder = 2;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.ALC_GROUP;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddCFCBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.Primary = true;
                cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb2.ReadingSource = Reading.CFC_PK;
                cb2.MMIOVariableIndex = 1;
                cb2.AttackRatio = 0.8f;
                cb2.DecayRatio = 0.1f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 2000;
                cb2.ShowHistory = true;
                cb2.MarkerColour = System.Drawing.Color.Yellow;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PaleTurquoise);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb2.ZOrder = 2;
                cb2.FontColour = System.Drawing.Color.Yellow;                
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb2);

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.TopLeft = cb2.TopLeft;
                cb.Size = cb2.Size;
                cb.ReadingSource = Reading.CFC_AV;
                cb.MMIOVariableIndex = 0;
                cb.ShowPeakValue = false;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 0;
                cb.ShowHistory = false;
                cb.MarkerColour = System.Drawing.Color.DarkGray;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PaleTurquoise);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb.ZOrder = 3;
                cb.ShowValue = false;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb.PostDrawItem = cb2;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.CFC;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddCFCGainBar(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.CFC_G;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Yellow;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PaleTurquoise);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(0, new PointF(0, 0));
                cb.ScaleCalibration.Add(20, new PointF(0.8f, 0));
                cb.ScaleCalibration.Add(25, new PointF(0.99f, 0));
                cb.ZOrder = 3;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb);               

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.CFC_GAIN;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddCompBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb2 = new clsBarItem();
                cb2.ParentID = ig.ID;
                cb2.Primary = true;
                cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb2.ReadingSource = Reading.COMP_PK;
                cb2.MMIOVariableIndex = 1;
                cb2.AttackRatio = 0.8f;
                cb2.DecayRatio = 0.1f;
                cb2.UpdateInterval = nMSupdate;
                cb2.HistoryDuration = 2000;
                cb2.ShowHistory = true;
                cb2.MarkerColour = System.Drawing.Color.Yellow;
                cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PeachPuff);
                cb2.Style = clsBarItem.BarStyle.Line;
                cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb2.ZOrder = 2;
                cb2.FontColour = System.Drawing.Color.Yellow;
                cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                addMeterItem(cb2);

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.TopLeft = cb2.TopLeft;
                cb.Size = cb2.Size;
                cb.ReadingSource = Reading.COMP;
                cb.MMIOVariableIndex = 0;
                cb.ShowPeakValue = false;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = false;
                cb.MarkerColour = System.Drawing.Color.DarkGray;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PeachPuff);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(-30, new PointF(0, 0));
                cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
                cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
                cb.ZOrder = 3;
                cb.ShowValue = false;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
                cb.PostDrawItem = cb2;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.COMP;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            //CPDR is the same as comp
            //public string AddCompanderBar(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            //{
            //    clsItemGroup ig = new clsItemGroup();
            //    if (restoreIg != null) ig.ID = restoreIg.ID;
            //    ig.ParentID = ID;

            //    clsBarItem cb2 = new clsBarItem();
            //    cb2.ParentID = ig.ID;
            //    cb2.Primary = true;
            //    cb2.TopLeft = new PointF(_fPadX, fTop + _fPadY);
            //    cb2.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
            //    cb2.ReadingSource = Reading.CPDR_PK;
            //    cb2.MMIOVariableIndex = 1;
            //    cb2.AttackRatio = 0.8f;
            //    cb2.DecayRatio = 0.1f;
            //    cb2.UpdateInterval = nMSupdate;
            //    cb2.HistoryDuration = 2000;
            //    cb2.ShowHistory = true;
            //    cb2.MarkerColour = System.Drawing.Color.Yellow;
            //    cb2.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PeachPuff);
            //    cb2.Style = clsBarItem.BarStyle.Line;
            //    cb2.ScaleCalibration.Add(-30, new PointF(0, 0));
            //    cb2.ScaleCalibration.Add(0, new PointF(0.665f, 0));
            //    cb2.ScaleCalibration.Add(12, new PointF(0.99f, 0));
            //    cb2.ZOrder = 2;
            //    cb2.FontColour = System.Drawing.Color.Yellow;
            //    cb2.Value = cb2.ScaleCalibration.OrderBy(p => p.Key).First().Key;
            //    cb2.HighPoint = cb2.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
            //    addMeterItem(cb2);

            //    clsBarItem cb = new clsBarItem();
            //    cb.ParentID = ig.ID;
            //    cb.TopLeft = cb2.TopLeft;
            //    cb.Size = cb2.Size;
            //    cb.ReadingSource = Reading.CPDR;
            //    cb.MMIOVariableIndex = 0;
            //    cb.ShowPeakValue = false;
            //    cb.AttackRatio = 0.8f;
            //    cb.DecayRatio = 0.1f;
            //    cb.UpdateInterval = nMSupdate;
            //    cb.HistoryDuration = 2000;
            //    cb.ShowHistory = false;
            //    cb.MarkerColour = System.Drawing.Color.DarkGray;
            //    cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.PeachPuff);
            //    cb.Style = clsBarItem.BarStyle.Line;
            //    cb.ScaleCalibration.Add(-30, new PointF(0, 0));
            //    cb.ScaleCalibration.Add(0, new PointF(0.665f, 0));
            //    cb.ScaleCalibration.Add(12, new PointF(0.99f, 0));
            //    cb.ZOrder = 3;
            //    cb.ShowValue = false;
            //    cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
            //    cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(1).Value;
            //    cb.PostDrawItem = cb2;
            //    addMeterItem(cb);

            //    clsScaleItem cs = new clsScaleItem();
            //    cs.ParentID = ig.ID;
            //    cs.TopLeft = cb.TopLeft;
            //    cs.Size = cb.Size;
            //    cs.ReadingSource = cb.ReadingSource;
            //    cs.ZOrder = 4;
            //    cs.ShowType = true;
            //    addMeterItem(cs);

            //    clsSolidColour sc = new clsSolidColour();
            //    sc.ParentID = ig.ID;
            //    sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
            //    sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
            //    sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
            //    sc.ZOrder = 1;
            //    addMeterItem(sc);

            //    fBottom = cb.TopLeft.Y + cb.Size.Height;

            //    ig.TopLeft = cb.TopLeft;
            //    ig.Size = new SizeF(cb.Size.Width, fBottom);
            //    ig.MeterType = MeterType.CPDR;
            //    ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

            //    clsFadeCover fc = getFadeCover(ig.ID);
            //    if (fc != null) addMeterItem(fc);

            //    addMeterItem(ig);

            //    return cb.ID;
            //}
            public string AddPWRBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                return AddPWRBar(nMSupdate, fTop, out fBottom, Reading.PWR, restoreIg);
            }
            public string AddREVPWRBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                return AddPWRBar(nMSupdate, fTop, out fBottom, Reading.REVERSE_PWR, restoreIg);
            }
            public string AddPWRBar(int nMSupdate, float fTop, out float fBottom, Reading reading,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = reading;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Yellow;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Red);
                cb.Style = clsBarItem.BarStyle.Line;

                //switch (CurrentPowerRating)
                //{
                //    case 500:
                //        {
                //            cb.ScaleCalibration.Add(0, new PointF(0, 0));
                //            cb.ScaleCalibration.Add(50, new PointF(0.1875f, 0));
                //            cb.ScaleCalibration.Add(100, new PointF(0.375f, 0));
                //            cb.ScaleCalibration.Add(250, new PointF(0.5625f, 0));
                //            cb.ScaleCalibration.Add(500, new PointF(0.75f, 0));
                //            cb.ScaleCalibration.Add(600, new PointF(0.99f, 0));
                //        }
                //        break;
                //    case 200:
                //        {
                //            cb.ScaleCalibration.Add(0, new PointF(0, 0));
                //            cb.ScaleCalibration.Add(10, new PointF(0.1875f, 0));
                //            cb.ScaleCalibration.Add(20, new PointF(0.375f, 0));
                //            cb.ScaleCalibration.Add(100, new PointF(0.5625f, 0));
                //            cb.ScaleCalibration.Add(200, new PointF(0.75f, 0));
                //            cb.ScaleCalibration.Add(240, new PointF(0.99f, 0));
                //        }
                //        break;
                    //case 100:
                    //    {
                            cb.ScaleCalibration.Add(0, new PointF(0, 0));
                            cb.ScaleCalibration.Add(5, new PointF(0.1875f, 0));
                            cb.ScaleCalibration.Add(10, new PointF(0.375f, 0));
                            cb.ScaleCalibration.Add(50, new PointF(0.5625f, 0));
                            cb.ScaleCalibration.Add(100, new PointF(0.75f, 0));
                            cb.ScaleCalibration.Add(120, new PointF(0.99f, 0));
                    //    }
                //        break;
                //    case 30:
                //        {
                //            cb.ScaleCalibration.Add(0, new PointF(0, 0));
                //            cb.ScaleCalibration.Add(5, new PointF(0.1875f, 0));
                //            cb.ScaleCalibration.Add(10, new PointF(0.375f, 0));
                //            cb.ScaleCalibration.Add(20, new PointF(0.5625f, 0));
                //            cb.ScaleCalibration.Add(30, new PointF(0.75f, 0));
                //            cb.ScaleCalibration.Add(50, new PointF(0.99f, 0));
                //        }
                //        break;
                //    case 15:
                //        {
                //            cb.ScaleCalibration.Add(0, new PointF(0, 0));
                //            cb.ScaleCalibration.Add(1, new PointF(0.1875f, 0));
                //            cb.ScaleCalibration.Add(5, new PointF(0.375f, 0));
                //            cb.ScaleCalibration.Add(10, new PointF(0.5625f, 0));
                //            cb.ScaleCalibration.Add(15, new PointF(0.75f, 0));
                //            cb.ScaleCalibration.Add(25, new PointF(0.99f, 0));
                //        }
                //        break;
                //    case 1:
                //        {
                //            cb.ScaleCalibration.Add(0, new PointF(0, 0));
                //            cb.ScaleCalibration.Add(0.1f, new PointF(0.1875f, 0));
                //            cb.ScaleCalibration.Add(0.25f, new PointF(0.375f, 0));
                //            cb.ScaleCalibration.Add(0.5f, new PointF(0.5625f, 0));
                //            cb.ScaleCalibration.Add(0.8f, new PointF(0.75f, 0));
                //            cb.ScaleCalibration.Add(1f, new PointF(0.99f, 0));

                //        }
                //        break;
                //}

                cb.NormaliseTo100W = true;
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 2;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(4).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                if (reading == Reading.PWR)
                    ig.MeterType = MeterType.PWR;
                else
                    ig.MeterType = MeterType.REVERSE_PWR;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddSWRBar(int nMSupdate, float fTop, out float fBottom,  clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if(restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBarItem cb = new clsBarItem();
                cb.ParentID = ig.ID;
                cb.Primary = true;
                cb.TopLeft = new PointF(_fPadX, fTop + _fPadY);
                cb.Size = new SizeF(1f - _fPadX * 2f, _fHeight);
                cb.ReadingSource = Reading.SWR;
                cb.MMIOVariableIndex = 0;
                cb.AttackRatio = 0.8f;
                cb.DecayRatio = 0.1f;
                cb.UpdateInterval = nMSupdate;
                cb.HistoryDuration = 2000;
                cb.ShowHistory = true;
                cb.MarkerColour = System.Drawing.Color.Yellow;
                cb.HistoryColour = System.Drawing.Color.FromArgb(128, System.Drawing.Color.Orange);
                cb.Style = clsBarItem.BarStyle.Line;
                cb.ScaleCalibration.Add(1, new PointF(0, 0));
                cb.ScaleCalibration.Add(1.5f, new PointF(0.25f, 0));
                cb.ScaleCalibration.Add(2, new PointF(0.5f, 0));
                cb.ScaleCalibration.Add(3, new PointF(0.75f, 0));
                cb.ScaleCalibration.Add(5, new PointF(0.99f, 0));
                cb.FontColour = System.Drawing.Color.Yellow;
                cb.ZOrder = 2;
                cb.Value = cb.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                cb.HighPoint = cb.ScaleCalibration.OrderBy(p => p.Key).ElementAt(3).Value;
                addMeterItem(cb);

                clsScaleItem cs = new clsScaleItem();
                cs.ParentID = ig.ID;
                cs.TopLeft = cb.TopLeft;
                cs.Size = cb.Size;
                cs.ReadingSource = cb.ReadingSource;
                cs.ZOrder = 4;
                cs.ShowType = true;
                addMeterItem(cs);

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(cb.TopLeft.X, cb.TopLeft.Y - _fHeight * 0.75f);
                sc.Size = new SizeF(cb.Size.Width, _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                fBottom = cb.TopLeft.Y + cb.Size.Height;

                ig.TopLeft = cb.TopLeft;
                ig.Size = new SizeF(cb.Size.Width, fBottom);
                ig.MeterType = MeterType.SWR;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return cb.ID;
            }
            public string AddBandButtons(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsBandButtonBox bb = new clsBandButtonBox(this);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 1f);

                bb.ZOrder = 1;
                bb.Columns = 3;                
                bb.Margin = 0.005f;
                bb.Radius = 0.01f;
                bb.HeightRatio = 0.5f;
                bb.Border = 0.005f;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.BAND_BUTTONS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return bb.ID;
            }
            public string AddDiscordButtons(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsDiscordButtonBox bb = new clsDiscordButtonBox(this);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 1f);

                bb.ZOrder = 1;
                bb.Columns = 3;
                bb.Margin = 0.005f;
                bb.Radius = 0.01f;
                bb.HeightRatio = 0.5f;
                bb.Border = 0.005f;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.DISCORD_BUTTONS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return bb.ID;
            }
            public string AddModeButtons(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsModeButtonBox bb = new clsModeButtonBox(this);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 1f);

                bb.ZOrder = 1;
                bb.Columns = 3;
                bb.Margin = 0.005f;
                bb.Radius = 0.01f;
                bb.HeightRatio = 0.5f;
                bb.Border = 0.005f;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.MODE_BUTTONS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return bb.ID;
            }
            public string AddFilterButtons(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsFilterButtonBox bb = new clsFilterButtonBox(this);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 1f);

                bb.ZOrder = 1;
                bb.Columns = 3;
                bb.Margin = 0.005f;
                bb.Radius = 0.01f;
                bb.HeightRatio = 0.5f;
                bb.Border = 0.005f;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.FILTER_BUTTONS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return bb.ID;
            }
            public string AddHistory(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsHistoryItem bb = new clsHistoryItem(this, ig);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 0.5f);

                bb.ZOrder = 1;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.HISTORY;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return bb.ID;
            }
            public string AddAntennaButtons(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsAntennaButtonBox bb = new clsAntennaButtonBox(this, ig);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 1f);

                bb.ZOrder = 1;
                bb.Columns = 3;
                bb.Margin = 0.005f;
                bb.Radius = 0.01f;
                bb.HeightRatio = 0.5f;
                bb.Border = 0.005f;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.ANTENNA_BUTTONS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);            

                return bb.ID;
            }
            public string AddTunestepButtons(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsTunestepButtons bb = new clsTunestepButtons(this, ig);
                bb.ParentID = ig.ID;

                bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                bb.Size = new SizeF(1f - _fPadX * 2f, 1f);

                bb.ZOrder = 1;
                bb.Columns = 3;
                bb.Margin = 0.005f;
                bb.Radius = 0.01f;
                bb.HeightRatio = 0.5f;
                bb.Border = 0.005f;
                addMeterItem(bb);

                fBottom = bb.TopLeft.Y + bb.Size.Height;

                ig.TopLeft = bb.TopLeft;
                ig.Size = new SizeF(bb.Size.Width, fBottom);
                ig.MeterType = MeterType.TUNESTEP_BUTTONS;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);

                addMeterItem(ig);

                return bb.ID;
            }
            public string AddVFODisplay(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsVfoDisplay vfo = new clsVfoDisplay(this);
                float height_multi = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH ? 1f : 2f;

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(_fPadX, fTop + _fPadY - _fHeight * 0.75f);
                if(vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH)
                    sc.Size = new SizeF(0.5f - _fPadX - (_fPadX * 0.5f), _fHeight + _fHeight * 0.75f);
                else
                    sc.Size = new SizeF(1f - _fPadX * 2f, (_fHeight + _fHeight * 0.75f) * height_multi);
                    
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                sc.Primary = true;
                addMeterItem(sc);

                clsSolidColour sc2 = new clsSolidColour();
                sc2.ParentID = ig.ID;                
                if (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH)
                {
                    sc2.TopLeft = new PointF(0.5f + (_fPadX * 0.5f), fTop + _fPadY - _fHeight * 0.75f);
                    sc2.Size = new SizeF(0.5f - _fPadX - (_fPadX * 0.5f), _fHeight + _fHeight * 0.75f);
                }
                else
                {
                    sc2.TopLeft = new PointF(_fPadX, fTop + _fPadY - _fHeight * 0.75f);
                    sc2.Size = new SizeF(1f - _fPadX * 2f, (_fHeight + _fHeight * 0.75f) * height_multi);
                }
                sc2.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc2.ZOrder = 1;
                sc2.Visible = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH;
                sc2.Primary = false;
                addMeterItem(sc2);

                //
                vfo.ParentID = ig.ID;
                vfo.TopLeft = sc.TopLeft;
                vfo.Size = new SizeF(1f - _fPadX * 2f, (_fHeight + _fHeight * 0.75f) * height_multi);
                //vfo.UpdateInterval = nMSupdate;  //Fixed by constructor, should not be changed by user
                //vfo.Primary = true;
                vfo.ZOrder = 2;
                addMeterItem(vfo);
                //

                fBottom = sc.TopLeft.Y + sc.Size.Height;

                ig.TopLeft = sc.TopLeft;
                ig.Size = new SizeF(sc.Size.Width + (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH ? sc2.Size.Width : 0f) + _fPadX, fBottom);
                ig.MeterType = MeterType.VFO_DISPLAY;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                //clsFadeCover fc = getFadeCover(ig.ID);
                //if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return sc.ID;
            }
            public string AddClock(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            {
                clsItemGroup ig = new clsItemGroup();
                if (restoreIg != null) ig.ID = restoreIg.ID;
                ig.ParentID = ID;

                clsSolidColour sc = new clsSolidColour();
                sc.ParentID = ig.ID;
                sc.TopLeft = new PointF(_fPadX, fTop + _fPadY - _fHeight * 0.75f);
                sc.Size = new SizeF(0.5f - _fPadX - (_fPadX * 0.5f), _fHeight + _fHeight * 0.75f);
                sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc.ZOrder = 1;
                addMeterItem(sc);

                clsSolidColour sc2 = new clsSolidColour();
                sc2.ParentID = ig.ID;
                sc2.TopLeft = new PointF(0.5f + (_fPadX * 0.5f), fTop + _fPadY - _fHeight * 0.75f);
                sc2.Size = new SizeF(0.5f - _fPadX - (_fPadX * 0.5f), _fHeight + _fHeight * 0.75f);
                sc2.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
                sc2.ZOrder = 1;
                addMeterItem(sc2);

                //
                clsClock clk = new clsClock();
                clk.ParentID = ig.ID;
                clk.TopLeft = sc.TopLeft;
                clk.Size = new SizeF(1f - _fPadX * 2f, _fHeight + _fHeight * 0.75f);
                //vfo.UpdateInterval = nMSupdate;  //Fixed by constructor, can not be changed by user
                clk.Primary = true;
                clk.ZOrder = 2;
                addMeterItem(clk);
                //

                fBottom = sc.TopLeft.Y + sc.Size.Height;

                ig.TopLeft = sc.TopLeft;
                ig.Size = new SizeF(sc.Size.Width + sc2.Size.Width + _fPadX, fBottom);
                ig.MeterType = MeterType.CLOCK;
                ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

                clsFadeCover fc = getFadeCover(ig.ID);
                if (fc != null) addMeterItem(fc);
                
                addMeterItem(ig);

                return sc.ID;
            }
            //public string AddSpectrum(int nMSupdate, float fTop, out float fBottom, clsItemGroup restoreIg = null)
            //{
            //    clsItemGroup ig = new clsItemGroup();
            //    if (restoreIg != null) ig.ID = restoreIg.ID;
            //    ig.ParentID = ID;

            //    clsSolidColour sc;
            //    sc = new clsSolidColour();
            //    sc.ParentID = ig.ID;
            //    sc.TopLeft = new PointF(_fPadX, fTop + _fPadY - _fHeight * 0.75f);
            //    sc.Size = new SizeF(1f - _fPadX * 2f, (_fHeight + _fHeight * 0.75f) * 8f);
            //    sc.Colour = System.Drawing.Color.FromArgb(32, 32, 32);
            //    sc.ZOrder = 1;
            //    addMeterItem(sc);

            //    clsSpectrum cst = new clsSpectrum();
            //    cst.ParentID = ig.ID;
            //    cst.Primary = true;
            //    cst.TopLeft = sc.TopLeft;
            //    cst.Size = sc.Size;
            //    cst.UpdateInterval = nMSupdate;
            //    cst.ZOrder = 2;
            //    addMeterItem(cst);

            //    //clsClickBox clb = new clsClickBox();
            //    //clb.ParentID = ig.ID;
            //    //clb.TopLeft = sc.TopLeft;
            //    //clb.Size = sc.Size;
            //    //clb.ChangesGroup = false;
            //    //clb.PerformsIncDec = true;
            //    //clb.RelatedMeterItem = cst;
            //    //addMeterItem(clb);

            //    fBottom = sc.TopLeft.Y + sc.Size.Height;

            //    ig.TopLeft = sc.TopLeft;
            //    ig.Size = new SizeF(sc.Size.Width, fBottom);
            //    ig.MeterType = MeterType.SPECTRUM;
            //    ig.Order = restoreIg == null ? numberOfMeterGroups() : restoreIg.Order;

            //    clsFadeCover fc = getFadeCover(ig.ID);
            //    if (fc != null) addMeterItem(fc);

            //    addMeterItem(ig);

            //    return sc.ID;
            //}
            #endregion
            public clsMeter(int rx, string sName, float XRatio = 1f, float YRatio = 1f)
            {
                // constructor
                _sId = Guid.NewGuid().ToString();
                _rx = rx;
                _name = sName;
                _enabled = true;
                _show_on_rx = true;
                _show_on_tx = true;
                _quickestRXUpdate = 250;
                _quickestTXUpdate = 250;
                _split = false;
                _vfoA = 0;
                _vfoB = 0;
                _vfoSub = 0;
                _txVfoB = false;
                _bandVfoA = Band.FIRST;
                _bandVfoB = Band.FIRST;
                _bandVfoASub = Band.FIRST;
                _bandTX = Band.FIRST;
                _modeVfoA = DSPMode.FIRST;
                _modeVfoB = DSPMode.FIRST;
                _filterVfoA = Filter.FIRST;
                _filterVfoB = Filter.FIRST;
                _filterVfoAname = "";
                _filterVfoBname = "";
                _rx2Enabled = false;
                _multiRxEnabled = false;
                _qso_start = DateTime.Now;
                _qso_end = DateTime.Now;

                _txeqEnabled = true;
                _levelerEnabled = true;
                _cfcEnabled = true;
                _compandEnabled = true;

                _quickSplitEnabled = false;

                //_fPadX = 0.02f;
                //_fPadY = 0.05f;
                _fPadX = 0.004f;
                _fPadY = 0.041f;

                _fHeight = 0.05f;

                _XRatio = XRatio;
                _YRatio = YRatio;

                _meterItems = new Dictionary<string, clsMeterItem>();
                _sortedMeterItemsForZOrder = null; // only after setupSortedZOrder is called
                _displayGroups = new List<string>();
                _displayGroup = 0;

                _band_group = BandGroups.GEN;

                _vfoA_band_text = "";
                _vfoB_band_text = "";
            }
            public BandGroups GetBandGroupFromBand(Band b)
            {
                RadioButtonTS r;

                switch (b)
                {
                    case Band.B160M:
                    case Band.B80M:
                    case Band.B60M:
                    case Band.B40M:
                    case Band.B30M:
                    case Band.B20M:
                    case Band.B17M:
                    case Band.B15M:
                    case Band.B12M:
                    case Band.B10M:
                    case Band.B6M:
                    case Band.B2M:
                        return BandGroups.HF;
                    case Band.WWV:
                        return BandGroups.HF;
                    case Band.BLMF:
                    case Band.B120M:
                    case Band.B90M:
                    case Band.B61M:
                    case Band.B49M:
                    case Band.B41M:
                    case Band.B31M:
                    case Band.B25M:
                    case Band.B22M:
                    case Band.B19M:
                    case Band.B16M:
                    case Band.B14M:
                    case Band.B13M:
                    case Band.B11M:
                        return BandGroups.GEN;
                    case Band.VHF0:
                    case Band.VHF1:
                    case Band.VHF2:
                    case Band.VHF3:
                    case Band.VHF4:
                    case Band.VHF5:
                    case Band.VHF6:
                    case Band.VHF7:
                    case Band.VHF8:
                    case Band.VHF9:
                    case Band.VHF10:
                    case Band.VHF11:
                    case Band.VHF12:
                    case Band.VHF13:
                        return BandGroups.VHF;
                    default:
                        return BandGroups.GEN;
                }
            }
            public void SetBandPanel(Console c, int rx, bool gen, bool hf, bool vhf)
            {
                if (c == null) return;
                if (rx > 1) return; // this does not happen for rx 2

                if (gen)
                {
                    hf = false;
                    vhf = false;
                }
                else if (hf)
                {
                    gen = false;
                    vhf = false;
                }
                else if (vhf)
                {
                    gen = false;
                    hf = false;
                }
                _console.BeginInvoke(new MethodInvoker(() =>
                {
                    if (gen && !c.BandGENSelected)
                        c.BandGENSelected = true;
                    else if (hf && !c.BandHFSelected)
                        c.BandHFSelected = true;
                    else if (vhf && !c.BandVHFSelected)
                        c.BandVHFSelected = true;
                }));
            }
            public void UpdateFilterButtons(Filter newFilter, string name)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems.Where(mis => mis.Value.ItemType == clsMeterItem.MeterItemType.FILTER_BUTTONS))
                    {
                        clsFilterButtonBox mi = (clsFilterButtonBox)mis.Value;

                        mi.FilterChanged(newFilter, name);
                    }
                }
            }
            public void InitFilterButtons()
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems.Where(mis => mis.Value.ItemType == clsMeterItem.MeterItemType.FILTER_BUTTONS))
                    {
                        clsFilterButtonBox mi = (clsFilterButtonBox)mis.Value;

                        mi.InitFilterButtons();
                    }
                }
            }
            //public void FilterNameDetails(Filter f, string new_name)
            //{
            //    if (_console == null) return;

            //    lock (_meterItemsLock)
            //    {
            //        foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems.Where(mis => mis.Value.ItemType == clsMeterItem.MeterItemType.FILTER_BUTTONS))
            //        {
            //            clsFilterButtonBox mi = (clsFilterButtonBox)mis.Value;

            //            mi.FilterNameChanged(f, new_name);
            //        }
            //    }
            //}
            public void ModeChanged(DSPMode oldMode, DSPMode newMode)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems)
                    {
                        clsMeterItem mi = (clsMeterItem)mis.Value;

                        mi.ModeChanged(oldMode, newMode);
                    }
                }
            }
            public void TuneStepIndexChanged(int old_index, int new_index)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems)
                    {
                        clsMeterItem mi = (clsMeterItem)mis.Value;

                        mi.TuneStepIndexChanged(old_index, new_index);
                    }
                }
            }
            public void BandChanged(Band oldBand, Band newBand)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems)
                    {
                        clsMeterItem mi = (clsMeterItem)mis.Value;

                        mi.BandChanged(oldBand, newBand);
                    }
                }
            }
            public void AntennasChanged(Band rx1_band, Band tx_band, double vfoa_freq, double tx_freq, int rxtx_swap = 0)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems.Where(mis => mis.Value.ItemType == clsMeterItem.MeterItemType.ANTENNA_BUTTONS))
                    {
                        clsAntennaButtonBox mi = (clsAntennaButtonBox)mis.Value;

                        mi.AntennasChanged(rx1_band, tx_band, vfoa_freq, tx_freq, rxtx_swap);
                    }
                }
            }
            public void BandPanelsChanged(bool gen, bool hf, bool vhf)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems.Where(mis => mis.Value.ItemType == clsMeterItem.MeterItemType.BAND_BUTTONS))
                    {
                        clsMeterItem mi = (clsMeterItem)mis.Value;

                        mi.BandPanelsChanged(gen, hf, vhf);
                    }
                }
            }
            public void VHFDetailsChanged(int idx, bool enabled, string text)
            {
                if (_console == null) return;

                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems.Where(mis => mis.Value.ItemType == clsMeterItem.MeterItemType.BAND_BUTTONS))
                    {
                        clsBandButtonBox mi = (clsBandButtonBox)mis.Value;

                        mi.VHFUpdate(idx, enabled, text);
                    }
                }
            }
            public void ZeroOut(bool bRxReadings, bool bTxReadings)
            {
                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems)
                    {
                        clsMeterItem mi = mis.Value;

                        mi.ClearHistory();

                        Dictionary<Reading, float> values = new Dictionary<Reading, float>();
                        bool bOk = mi.ZeroOut(ref values, _rx); // get value to zero out the meter, returns false if no scale

                        if (bOk)
                        {
                            foreach (KeyValuePair<Reading, float> kvp in values)
                            {
                                if (bRxReadings || bTxReadings) setReadingForced(RX, kvp.Key, kvp.Value);
                            }
                        }
                    }
                }
            }
            public void KeyDown(Keys keycode)
            {
                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems)
                    {
                        clsMeterItem mi = mis.Value;
                        mi.KeyDown(keycode);
                    }
                }
            }
            public void KeyUp(Keys keycode)
            {
                lock (_meterItemsLock)
                {
                    foreach (KeyValuePair<string, clsMeterItem> mis in _meterItems)
                    {
                        clsMeterItem mi = mis.Value;
                        mi.KeyUp(keycode);
                    }
                }
            }
            public bool TryParse(string str)
            {
                bool bOk = false;
                int rx=0, displaygroup=0;
                float xRatio=0, yRatio=0, fPadX=0, fPadY=0, fHeight=0;

                if (str != "")
                {
                    string[] tmp = str.Split('|');
                    if (tmp.Length == 9)
                    {
                        bOk = tmp[0] != "";
                        if (bOk) ID = tmp[0];
                        if (bOk) _name = tmp[1];
                        if (bOk) int.TryParse(tmp[2], out rx);
                        if (bOk) RX = rx;
                        if (bOk) bOk = float.TryParse(tmp[3], out xRatio);
                        if (bOk) bOk = float.TryParse(tmp[4], out yRatio);
                        if (bOk)
                        {
                            _XRatio = xRatio;
                            _YRatio = yRatio;
                        }
                        if (bOk) int.TryParse(tmp[5], out displaygroup);
                        if (bOk) _displayGroup = displaygroup;
                        if (bOk) bOk = float.TryParse(tmp[6], out fPadX);
                        if (bOk) bOk = float.TryParse(tmp[7], out fPadY);
                        if (bOk) bOk = float.TryParse(tmp[8], out fHeight);
                        if (bOk)
                        {
                            //_fPadX = fPadX;
                            //_fPadY = fPadY;
                            _fPadX = 0.004f;
                            _fPadY = 0.041f;

                            _fHeight = fHeight;
                        }

                    }
                }

                return bOk;
            }
            private int numberOfMeterGroups()
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return 0;

                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);

                    return items.Count;
                }
            }
            private void removeMeterItem(string sId, bool bRebuild = false)
            {
                lock (_meterItemsLock)
                {
                    // remove this item, plus all items that are children

                    Dictionary<string, clsMeterItem> items = itemsFromID(sId);
                    if (items == null || items.Count == 0) return;

                    List<string> toRemove = new List<string>();
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        toRemove.Add(kvp.Value.ID);
                    }
                    items.Clear();

                    foreach (string id in toRemove)
                    {
                        _meterItems[id].Removing();
                        _meterItems.Remove(id);
                    }
                    toRemove.Clear();

                    if (bRebuild) Rebuild();
                }
            }
            public void RemoveMeterType(MeterType mt, int order, bool bRebuild = false)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return;

                    //special for ananMM
                    if(mt == MeterType.ANANMM)
                    {
                        RemoveDisplayGroup("ALL");
                        RemoveDisplayGroup("PWR/SWR");
                        RemoveDisplayGroup("Comp");
                        RemoveDisplayGroup("ALC");
                        RemoveDisplayGroup("Volts/Amps");
                    }
                    //
                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach(KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        int nOrder = -1;

                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null && ig.MeterType == mt && ig.Order == order) {
                            nOrder = ig.Order;
                            removeMeterItem(ig.ID, false);
                            //if (mt == MeterType.SPACER || mt == MeterType.TEXT_OVERLAY) order = -9999; // only remove the single
                            order = -9999; // prevents any more from being removed
                        }

                        if(nOrder >= 0)
                        {
                            Dictionary<string, clsMeterItem> tmpItems = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                            foreach(KeyValuePair<string, clsMeterItem> tmpKvp in tmpItems){
                                clsItemGroup tmpIg = tmpKvp.Value as clsItemGroup;
                                if (tmpIg != null && tmpIg.Order > nOrder)
                                    tmpIg.Order--;
                            }
                        }
                    }

                    if (bRebuild) Rebuild();
                }
            }
            public void RemoveAllMeterTypes(bool bRebuild = false)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return;

                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null)
                        {
                            removeMeterItem(ig.ID, false);
                        }
                    }

                    if (bRebuild) Rebuild();
                }
            }
            public bool HasMeterType(MeterType mt)
            {
                lock (_meterItemsLock)
                { 
                    if (_meterItems == null) return false;

                    //IEnumerable<KeyValuePair<string, clsMeterItem>> items = _meterItems.Where(o => o.Value.ItemType == MeterItemType.ITEM_GROUP);
                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null && ig.MeterType == mt) return true;
                    }

                    return false;
                }
            }
            public void DisableMeterType(MeterType mt, bool bDisable)
            {
                lock (_meterItemsLock)
                {
                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;

                        if (ig.MeterType == mt)
                        {
                            Dictionary<string, clsMeterItem> mtItems = itemsFromID(ig.ID, false, true);

                            foreach (KeyValuePair<string, clsMeterItem> kvpmi in mtItems)
                            {
                                clsMeterItem mi = kvpmi.Value as clsMeterItem;
                                mi.Disabled = bDisable;
                            }
                        }
                    }
                }
            }
            public string MeterGroupID(MeterType mt = MeterType.NONE, int order = -1)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return "";

                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null && (ig.MeterType == mt || mt == MeterType.NONE) && (order == -1 || ig.Order == order)) return ig.ID;
                    }

                    return "";
                }
            }
            public void ApplySettingsForMeterGroup(MeterType mt, clsIGSettings igs, int order = -1)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return;

                    bool bRebuild = false;

                    Dictionary<string, clsMeterItem> itemGroups = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in itemGroups)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null && ig.MeterType == mt && (order == -1 || ig.Order == order))
                        {
                            switch (mt)
                            {
                                case MeterType.HISTORY:
                                    {
                                        bRebuild = true;
                                        float padding = 0f;
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.HISTORY))
                                        {
                                            clsHistoryItem his = me.Value as clsHistoryItem;
                                            if (his == null) continue;

                                            his.VerticalRatio = igs.GetSetting<float>("history_vertical_ratio", true, 0.130f, 1f, 0.5f);
                                            his.BackColour = igs.GetSetting("history_background_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Black);
                                            his.UpdateInterval = (int)igs.GetSetting<float>("history_update", true, 50f, 10000f, 0.5f);
                                            his.KeepFor = (int)igs.GetSetting<float>("history_keep_for", true, 1f, 86400f, 20f);

                                            his.Reading0 = igs.GetSetting<Reading>("history_reading_0", false, Reading.NONE, Reading.NONE, Reading.SIGNAL_STRENGTH);
                                            his.Reading1 = igs.GetSetting<Reading>("history_reading_1", false, Reading.NONE, Reading.NONE, Reading.SIGNAL_STRENGTH);

                                            his.AutoScale0 = igs.GetSetting<bool>("history_auto_scale_0", false, false, false, true); // needs to be before the min0 manuals
                                            his.Min0Manual = igs.GetSetting<float>("history_min_0", true, -10000f, 10000f, -150f);
                                            his.Max0Manual = igs.GetSetting<float>("history_max_0", true, -10000f, 10000f, 0f);

                                            his.ShowScale1 = igs.GetSetting<bool>("history_show_scale_1", false, false, false, true);

                                            his.AutoScale1 = igs.GetSetting<bool>("history_auto_scale_1", false, false, false, true); // needs to be before the min0 manuals
                                            his.Min1Manual = igs.GetSetting<float>("history_min_1", true, -10000f, 10000f, -150f);
                                            his.Max1Manual = igs.GetSetting<float>("history_max_1", true, -10000f, 10000f, 0f);

                                            his.Axis0MMIOGuid = igs.GetMMIOGuid(0);
                                            his.Axis0MMIOVariable = igs.GetMMIOVariable(0);
                                            his.Axis1MMIOGuid = igs.GetMMIOGuid(1);
                                            his.Axis1MMIOVariable = igs.GetMMIOVariable(1);

                                            his.Axis0Colour = igs.GetSetting("history_colour_0", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Red);
                                            his.Axis1Colour = igs.GetSetting("history_colour_1", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Yellow);

                                            his.LinesColour = igs.GetSetting("history_colour_lines", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.White);
                                            his.TimeColour = igs.GetSetting("history_colour_time", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Gray);

                                            his.FadeOnRx = igs.FadeOnRx;
                                            his.FadeOnTx = igs.FadeOnTx;

                                            his.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                            his.Size = new SizeF(ig.Size.Width, his.VerticalRatio);

                                            padding += his.Size.Height;
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, padding + (_fPadY - (_fHeight * 0.75f)));

                                        // recalc bounds for fade overlay cover as these will change
                                        System.Drawing.RectangleF eyeBounds = getBounds(ig.ID);
                                        if (!eyeBounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(ig.Size.Width, padding);

                                                fc.FadeOnRx = igs.FadeOnRx;
                                                fc.FadeOnTx = igs.FadeOnTx;
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.TUNESTEP_BUTTONS:
                                case MeterType.ANTENNA_BUTTONS:
                                case MeterType.FILTER_BUTTONS:
                                case MeterType.MODE_BUTTONS:
                                case MeterType.BAND_BUTTONS:
                                case MeterType.DISCORD_BUTTONS:
                                    {
                                        bRebuild = true;

                                        float height = 0f;
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        clsMeterItem.MeterItemType mit = clsMeterItem.MeterItemType.BASE;
                                        switch (mt)
                                        {
                                            case MeterType.TUNESTEP_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.TUNESTEP_BUTTONS;
                                                break;
                                            case MeterType.ANTENNA_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.ANTENNA_BUTTONS;
                                                break;
                                            case MeterType.FILTER_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.FILTER_BUTTONS;
                                                break;
                                            case MeterType.MODE_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.MODE_BUTTONS;
                                                break;
                                            case MeterType.BAND_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.BAND_BUTTONS;
                                                break;
                                            case MeterType.DISCORD_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.DISCORD_BUTTONS;
                                                break;
                                        }
                                        if (mit == clsMeterItem.MeterItemType.BASE) continue; // skip
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == mit))
                                        {
                                            clsButtonBox bb = me.Value as clsButtonBox;
                                            if (bb == null) continue;

                                            bb.RebuildButtons = false;

                                            bool use_indicator = igs.GetSetting<bool>("buttonbox_use_indicator", false, false, false, false);
                                            float indicator_width = igs.GetSetting<float>("buttonbox_indicator_border", true, 0, 1f, 0.5f) / 10f;
                                            System.Drawing.Color on_colour = igs.GetSetting("buttonbox_on_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.CornflowerBlue);
                                            System.Drawing.Color off_colour = igs.GetSetting("buttonbox_off_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.CornflowerBlue);

                                            System.Drawing.Color fill_colour = igs.GetSetting("buttonbox_fill_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Black);
                                            System.Drawing.Color hover_colour = igs.GetSetting("buttonbox_hover_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.LightGray);
                                            System.Drawing.Color border_colour = igs.GetSetting("buttonbox_border_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.White);

                                            System.Drawing.Color click_colour = igs.GetSetting("buttonbox_click_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Orange);
                                            System.Drawing.Color font_colour = igs.GetSetting("buttonbox_font_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.White);

                                            bool use_off_colour = igs.GetSetting<bool>("buttonbox_use_off_colour", false, false, false, false);

                                            clsButtonBox.IndicatorType indicator_type = igs.GetSetting<clsButtonBox.IndicatorType>("buttonbox_indicator_type", true, clsButtonBox.IndicatorType.RING, clsButtonBox.IndicatorType.LAST, clsButtonBox.IndicatorType.RING);

                                            for (int button = 0; button < bb.Buttons; button++)
                                            {
                                                bb.SetUseIndicator(0, button, use_indicator);
                                                bb.SetIndicatorWidth(0, button, indicator_width);
                                                bb.SetOnColour(0, button, on_colour);
                                                bb.SetOffColour(0, button, off_colour);
                                                bb.SetFillColour(0, button, fill_colour);
                                                bb.SetHoverColour(0, button, hover_colour);
                                                bb.SetBorderColour(0, button, border_colour);
                                                bb.SetUseOffColour(0, button, use_off_colour);
                                                bb.SetFontFamily(0, button, igs.FontFamily1);
                                                bb.SetFontStyle(0, button, igs.FontStyle1);
                                                bb.SetIndicatorType(0, button, indicator_type);
                                                bb.SetClickColour(0, button, click_colour);
                                                bb.SetFontColour(0, button, font_colour);
                                            }

                                            bb.Columns = igs.GetSetting<int>("buttonbox_columns", true, 1, 15, 3);
                                            bb.Border = igs.GetSetting<float>("buttonbox_border", true, 0f, 1f, 0.05f) / 10f;
                                            bb.Margin = igs.GetSetting<float>("buttonbox_margin", true, 0f, 1f, 0f) / 10f;
                                            bb.Radius = igs.GetSetting<float>("buttonbox_radius", true, 0f, 2f, 0f) / 10f;
                                            bb.HeightRatio = igs.GetSetting<float>("buttonbox_height_ratio", true, 0.01f, 2f, 0.5f);

                                            bb.FontScale = igs.GetSetting<float>("buttonbox_font_scale", true, 0.01f, 2f, 1f);
                                            bb.FontShiftX = igs.GetSetting<float>("buttonbox_font_shift_x", true, -0.25f, 0.25f, 0f);
                                            bb.FontShiftY = igs.GetSetting<float>("buttonbox_font_shift_y", true, -0.25f, 0.25f, 0f);

                                            if(mt == MeterType.TUNESTEP_BUTTONS)
                                            {                                                
                                                bb.VisibleBits = igs.GetSetting<int>("buttonbox_tunestep_bitfield", true, 0, int.MaxValue, 0);
                                            }
                                            else if (mt == MeterType.ANTENNA_BUTTONS)
                                            {
                                                int bits = 0;
                                                if (igs.GetSetting<bool>("buttonbox_rx1", false, false, false, true)) bits |= 1 << 0; // RX1
                                                if (igs.GetSetting<bool>("buttonbox_rx2", false, false, false, true)) bits |= 1 << 1; // RX2
                                                if (igs.GetSetting<bool>("buttonbox_rx3", false, false, false, true)) bits |= 1 << 2; // RX3
                                                if (igs.GetSetting<bool>("buttonbox_byp", false, false, false, true)) bits |= 1 << 3; // BYP
                                                if (igs.GetSetting<bool>("buttonbox_ext1", false, false, false, true)) bits |= 1 << 4; // EXT1
                                                if (igs.GetSetting<bool>("buttonbox_xvtr", false, false, false, true)) bits |= 1 << 5; // XVTR
                                                if (igs.GetSetting<bool>("buttonbox_tx1", false, false, false, true)) bits |= 1 << 6; // TX1
                                                if (igs.GetSetting<bool>("buttonbox_tx2", false, false, false, true)) bits |= 1 << 7; // TX2
                                                if (igs.GetSetting<bool>("buttonbox_tx3", false, false, false, true)) bits |= 1 << 8; // TX3
                                                if (igs.GetSetting<bool>("buttonbox_rxtxant", false, false, false, true)) bits |= 1 << 9; // RXTXANT
                                                bb.VisibleBits = bits;
                                            }

                                            bb.FadeOnRx = igs.FadeOnRx;
                                            bb.FadeOnTx = igs.FadeOnTx;

                                            bb.RebuildButtons = true;
                                            bb.Columns = bb.Columns; // just to force a rebuild

                                            height += bb.Size.Height;
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, height + (_fPadY - (_fHeight * 0.75f)));

                                        // recalc bounds for fade overlay cover as these will change as bb changes
                                        System.Drawing.RectangleF bounds = getBounds(ig.ID);
                                        if (!bounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(ig.Size.Width, height);

                                                fc.FadeOnRx = igs.FadeOnRx;
                                                fc.FadeOnTx = igs.FadeOnTx;
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.WEB_IMAGE:
                                    {
                                        bRebuild = true; // alwayys cause a rebuild

                                        float padding = 0f;

                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.WEB_IMAGE))
                                        {
                                            clsWebImage webimg = me.Value as clsWebImage;
                                            if (webimg == null) continue;

                                            webimg.FadeOnRx = igs.FadeOnRx;
                                            webimg.FadeOnTx = igs.FadeOnTx;
                                            webimg.URL = igs.Text1;
                                            webimg.SecondsInterval = igs.UpdateInterval;
                                            webimg.BypassCache = igs.DarkMode;
                                            webimg.WidthScale = igs.EyeScale;

                                            //webimg.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                            webimg.TopLeft = new PointF(0.5f - (igs.EyeScale / 2f), _fPadY - (_fHeight * 0.75f));
                                            webimg.Size = new SizeF(/*ig.Size.Width*/ igs.EyeScale, webimg.ScaleSize/*ig.Size.Height * igs.EyeScale*//*igs.SpacerPadding*/);

                                            padding += webimg.ScaleSize;// ig.Size.Height * igs.EyeScale;//igs.SpacerPadding;
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, padding + (_fPadY - (_fHeight * 0.75f)));

                                        // recalc bounds for fade overlay cover as these will change as spacer changes
                                        System.Drawing.RectangleF bounds = getBounds(ig.ID);
                                        if (!bounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(ig.Size.Width, padding);

                                                fc.FadeOnRx = igs.FadeOnRx;
                                                fc.FadeOnTx = igs.FadeOnTx;
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.ROTATOR:
                                    {
                                        bRebuild = true;
                                        float padding = 0f;
                                        string imageName = "";
                                        string mapName = "";
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ROTATOR))
                                        {
                                            clsRotatorItem rotator = me.Value as clsRotatorItem;
                                            if (rotator == null) continue; // skip

                                            if (rotator.MMIOVariableIndex != -1)
                                            {
                                                rotator.MMIOGuid = igs.GetMMIOGuid(rotator.MMIOVariableIndex);
                                                rotator.MMIOVariable = igs.GetMMIOVariable(rotator.MMIOVariableIndex);
                                            }
                                            else
                                            {
                                                rotator.MMIOGuid = Guid.Empty;
                                                rotator.MMIOVariable = "--DEFAULT--";
                                            }
                                            
                                            rotator.UpdateInterval = igs.UpdateInterval;
                                            rotator.ArrowColour = igs.TitleColor;
                                            rotator.BigBlobColour = igs.MarkerColour;
                                            rotator.SmallBlobColour = igs.SubMarkerColour;
                                            rotator.ShowBeamWidth = igs.ShowMarker;
                                            rotator.BeamWidthColour = igs.LowColor;
                                            rotator.OuterTextColour = igs.HighColor;
                                            rotator.ShowCardinals = igs.ShowHistory;
                                            rotator.ViewMode = (clsRotatorItem.RotatorMode)igs.HistoryDuration;
                                            if (rotator.ViewMode < clsRotatorItem.RotatorMode.AZ || rotator.ViewMode > clsRotatorItem.RotatorMode.BOTH) rotator.ViewMode = clsRotatorItem.RotatorMode.AZ;
                                            rotator.FadeOnRx = igs.FadeOnRx;
                                            rotator.FadeOnTx = igs.FadeOnTx;
                                            rotator.BeamWidth = igs.AttackRatio;
                                            rotator.Padding = igs.EyeScale;
                                            rotator.AllowControl = igs.ShowType;
                                            rotator.ControlColour = igs.HistoryColor;
                                            rotator.AZControlString = igs.Text1;
                                            rotator.ELEControlString = igs.Text2;
                                            rotator.STOPControlString = igs.FontFamily1;
                                            rotator.DataOutMMIOGuid = igs.GetMMIOGuid(2);
                                            rotator.BeamWidthAlpha = igs.GetSetting<float>("rotator_beamwidth_alpha", true, 0, 1f, 0.6f);
                                            imageName = rotator.ImageName;
                                            mapName = rotator.MapName;
                                            if (rotator.ViewMode == clsRotatorItem.RotatorMode.BOTH)
                                            {
                                                padding = 0.5f;
                                                rotator.TopLeft = new PointF(0f, _fPadY - (_fHeight * 0.75f));
                                                rotator.Size = new SizeF(1f, padding);
                                            }
                                            else
                                            {
                                                padding = rotator.Padding;
                                                rotator.TopLeft = new PointF(0.5f - (padding / 2f), _fPadY - (_fHeight * 0.75f));
                                                rotator.Size = new SizeF(padding, padding);
                                            }
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, padding + (_fPadY - (_fHeight * 0.75f)));
                                        foreach (KeyValuePair<string, clsMeterItem> img in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.IMAGE))
                                        {
                                            clsImage image = img.Value as clsImage;
                                            if (image == null) continue;

                                            if (image.ZOrder == 3) //grid
                                            {
                                                if ((clsRotatorItem.RotatorMode)igs.HistoryDuration == clsRotatorItem.RotatorMode.BOTH)
                                                {
                                                    image.TopLeft = new PointF(0, _fPadY - (_fHeight * 0.75f));
                                                    image.Size = new SizeF(1f, 0.5f);
                                                }
                                                else
                                                {
                                                    image.TopLeft = new PointF(0.5f - (igs.EyeScale / 2f), _fPadY - (_fHeight * 0.75f));
                                                    image.Size = new SizeF(igs.EyeScale, igs.EyeScale);
                                                }

                                                image.ImageName = imageName;
                                                image.FadeOnRx = igs.FadeOnRx;
                                                image.FadeOnTx = igs.FadeOnTx;
                                                image.DarkMode = igs.DarkMode;
                                            }
                                            else if(image.ZOrder == 2) // map
                                            {
                                                PointF centre_tmp;
                                                if ((clsRotatorItem.RotatorMode)igs.HistoryDuration == clsRotatorItem.RotatorMode.BOTH)
                                                {
                                                    centre_tmp = new PointF(0.085f + (0.38f / 2f), _fPadY - (_fHeight * 0.75f) + 0.06f + (0.38f / 2f)); // precalc from existing setup
                                                    image.Size = new SizeF(0.405f, 0.405f);
                                                }
                                                else
                                                {
                                                    centre_tmp = new PointF(0.5f - (igs.EyeScale / 2f) + (0.12f * igs.EyeScale) + (igs.EyeScale * 0.76f / 2f), _fPadY - (_fHeight * 0.75f) + (0.12f * igs.EyeScale) + (igs.EyeScale * 0.76f / 2f)); // precalc from existing setup
                                                    image.Size = new SizeF(igs.EyeScale * 0.81f, igs.EyeScale * 0.81f);
                                                }
                                                image.TopLeft = new PointF(centre_tmp.X - (image.Size.Width / 2f), centre_tmp.Y - (image.Size.Height / 2f));

                                                image.ClippedEllipse = true;
                                                image.ClipEllipseCentre = new PointF(0.5f, 0.5f);
                                                image.ClipEllipseRadius = new SizeF(0.47f, 0.47f);
                                                image.ImageName = mapName;
                                                image.FadeOnRx = igs.FadeOnRx;
                                                image.FadeOnTx = igs.FadeOnTx;
                                                image.DarkMode = igs.DarkMode;
                                            }
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColour = sc.Value as clsSolidColour;
                                            if (solidColour == null) continue;

                                            solidColour.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                            solidColour.Size = new SizeF(ig.Size.Width, padding);

                                            solidColour.FadeOnRx = igs.FadeOnRx;
                                            solidColour.FadeOnTx = igs.FadeOnTx;
                                            solidColour.Colour = igs.Colour;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue;

                                            fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                            fc.Size = new SizeF(ig.Size.Width, padding);

                                            fc.FadeOnRx = igs.FadeOnRx;
                                            fc.FadeOnTx = igs.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.LED:
                                    {
                                        bRebuild = true; // alwayys cause a rebuild as we relocate the text overlay each time

                                        float padding = 0f;

                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.LED))
                                        {
                                            clsLed led = me.Value as clsLed;
                                            if (led == null) continue;

                                            led.FadeOnRx = igs.FadeOnRx;
                                            led.FadeOnTx = igs.FadeOnTx;
                                            led.TrueColour = igs.Colour;
                                            led.FalseColour = igs.MarkerColour;

                                            led.PanelBackColour1 = igs.TitleColor;
                                            led.PanelBackColour2 = igs.HistoryColor;
                                            led.ShowBackPanel = igs.ShowSubMarker;

                                            led.OffsetX = igs.EyeScale;
                                            led.OffsetY = igs.EyeBezelScale;
                                            led.SizeX = igs.AttackRatio;
                                            led.SizeY = igs.DecayRatio;

                                            if (igs.UpdateInterval < 50) igs.UpdateInterval = 100; // when it hasnt been set
                                            led.UpdateInterval = igs.UpdateInterval;

                                            led.Condition = igs.Text1;

                                            led.Padding = igs.SpacerPadding;

                                            led.ShowTrue = igs.PeakHold;
                                            led.ShowFalse = igs.ShowMarker;
                                            switch (igs.IgnoreHistoryDuration)
                                            {
                                                case 0:
                                                    led.Blink = false;
                                                    led.Pulsate = false;
                                                    break;
                                                case 1:
                                                    led.Blink = true;
                                                    led.Pulsate = false;
                                                    break;
                                                case 2:
                                                    led.Blink = false;
                                                    led.Pulsate = true;
                                                    break;
                                            }

                                            if (igs.ShowSubMarker)
                                            {
                                                led.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                led.Size = new SizeF(ig.Size.Width, igs.SpacerPadding);
                                                padding += igs.SpacerPadding;
                                            }
                                            else
                                            {
                                                led.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                led.Size = new SizeF(0, 0);
                                            }

                                            led.NoTxTrue = igs.GetSetting<bool>("led_notx_true", false, false, false, false);
                                            led.NoTxFalse = igs.GetSetting<bool>("led_notx_false", false, false, false, false);
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, padding == 0f ? 0 : padding + (_fPadY - (_fHeight * 0.75f)));

                                        // recalc bounds for fade overlay cover as these will change as spacer changes
                                        System.Drawing.RectangleF bounds = getBounds(ig.ID);
                                        if (!bounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(ig.Size.Width, padding);

                                                fc.FadeOnRx = igs.FadeOnRx;
                                                fc.FadeOnTx = igs.FadeOnTx;
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.TEXT_OVERLAY:
                                    {
                                        bRebuild = true; // alwayys cause a rebuild as we relocate the text overlay each time

                                        float padding = 0f;

                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.TEXT_OVERLAY))
                                        {
                                            clsTextOverlay text_overlay = me.Value as clsTextOverlay;
                                            if (text_overlay == null) continue;

                                            text_overlay.FadeOnRx = igs.FadeOnRx;
                                            text_overlay.FadeOnTx = igs.FadeOnTx;
                                            text_overlay.TextColour1 = igs.Colour;
                                            text_overlay.TextColour2 = igs.MarkerColour;
                                            text_overlay.TextBackColour1 = igs.SubMarkerColour;
                                            text_overlay.ShowTextBackColour1 = igs.ShowMarker;
                                            text_overlay.TextBackColour2 = igs.PeakValueColour;
                                            text_overlay.ShowTextBackColour2 = igs.ShowType;

                                            text_overlay.PanelBackColour1 = igs.TitleColor;
                                            text_overlay.PanelBackColour2 = igs.HistoryColor;
                                            text_overlay.ShowBackPanel = igs.ShowSubMarker;

                                            text_overlay.TextXOffset1 = igs.EyeScale;
                                            text_overlay.TextYOffset1 = igs.EyeBezelScale;
                                            text_overlay.TextXOffset2 = igs.AttackRatio;
                                            text_overlay.TextYOffset2 = igs.DecayRatio;

                                            text_overlay.Text1 = igs.Text1;
                                            text_overlay.Text2 = igs.Text2;

                                            text_overlay.FontFamily1 = igs.FontFamily1;
                                            text_overlay.Style1 = igs.FontStyle1;
                                            text_overlay.FontSize1 = igs.FontSize1;
                                            text_overlay.FontFamily2 = igs.FontFamily2;
                                            text_overlay.Style2 = igs.FontStyle2;
                                            text_overlay.FontSize2 = igs.FontSize2;

                                            text_overlay.Padding = igs.SpacerPadding;

                                            if (igs.ShowSubMarker)
                                            {
                                                text_overlay.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                text_overlay.Size = new SizeF(ig.Size.Width, igs.SpacerPadding);
                                                padding += igs.SpacerPadding;
                                            }
                                            else
                                            {
                                                text_overlay.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                text_overlay.Size = new SizeF(0, 0);
                                            }
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, padding == 0f ? 0 : padding + (_fPadY - (_fHeight * 0.75f)));

                                        // recalc bounds for fade overlay cover as these will change as spacer changes
                                        System.Drawing.RectangleF bounds = getBounds(ig.ID);
                                        if (!bounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(ig.Size.Width, padding);

                                                fc.FadeOnRx = igs.FadeOnRx;
                                                fc.FadeOnTx = igs.FadeOnTx;
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.DATA_OUT:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.DATA_OUT))
                                        {
                                            clsDataOut data_out = me.Value as clsDataOut;
                                            if (data_out == null) continue;

                                            data_out.MMIOGuid = igs.GetMMIOGuid(0);
                                            data_out.UpdateInterval = igs.UpdateInterval;
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, 0);
                                    }
                                    break;
                                case MeterType.SPACER:
                                    {
                                        bRebuild = true; // alwayys cause a rebuild as we relocate the spacer each time

                                        float padding = 0f;

                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SPACER))
                                        {
                                            clsSpacerItem spacer = me.Value as clsSpacerItem;
                                            if (spacer == null) continue;

                                            spacer.FadeOnRx = igs.FadeOnRx;
                                            spacer.FadeOnTx = igs.FadeOnTx;
                                            spacer.Colour1 = igs.Colour;
                                            spacer.Colour2 = igs.MarkerColour;
                                            spacer.Padding = igs.SpacerPadding;
                                            
                                            spacer.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                            spacer.Size = new SizeF(ig.Size.Width, igs.SpacerPadding);

                                            padding += igs.SpacerPadding;
                                        }
                                        ig.Size = new SizeF(ig.Size.Width, padding + (_fPadY - (_fHeight * 0.75f)));
                                        
                                        // recalc bounds for fade overlay cover as these will change as spacer changes
                                        System.Drawing.RectangleF eyeBounds = getBounds(ig.ID);
                                        if (!eyeBounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(ig.Size.Width, padding);

                                                fc.FadeOnRx = igs.FadeOnRx;
                                                fc.FadeOnTx = igs.FadeOnTx;
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.MAGIC_EYE:
                                    {
                                        float fLargest = Math.Max(igs.EyeScale, igs.EyeBezelScale);
                                        
                                        if (fLargest != ig.Size.Width)
                                        {
                                            ig.TopLeft = new PointF(0.5f - (fLargest / 2f), _fPadY - (_fHeight * 0.75f));
                                            ig.Size = new SizeF(fLargest, ig.TopLeft.Y + fLargest);                                           
                                        }

                                        bRebuild = true; // alwayys cause a rebuild as we relocate the eye + image each time

                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.MAGIC_EYE))
                                        {
                                            clsMagicEyeItem magicEye = me.Value as clsMagicEyeItem;
                                            if (magicEye == null) continue;

                                            if (magicEye.MMIOVariableIndex != -1)
                                            {
                                                magicEye.MMIOGuid = igs.GetMMIOGuid(magicEye.MMIOVariableIndex);
                                                magicEye.MMIOVariable = igs.GetMMIOVariable(magicEye.MMIOVariableIndex);
                                            }
                                            else
                                            {
                                                magicEye.MMIOGuid = Guid.Empty;
                                                magicEye.MMIOVariable = "--DEFAULT--";
                                            }

                                            magicEye.UpdateInterval = igs.UpdateInterval;
                                            magicEye.AttackRatio = igs.AttackRatio;
                                            magicEye.DecayRatio = igs.DecayRatio;
                                            magicEye.HistoryDuration = igs.HistoryDuration;
                                            magicEye.HistoryColour = igs.HistoryColor;
                                            magicEye.ShowHistory = igs.ShowHistory;
                                            magicEye.FadeOnRx = igs.FadeOnRx;
                                            magicEye.FadeOnTx = igs.FadeOnTx;
                                            //bi.Style = igs.BarStyle;
                                            //bi.MarkerColour = igs.MarkerColour;
                                            //bi.PeakHold = igs.PeakHold;
                                            //bi.PeakHoldMarkerColour = igs.PeakHoldMarkerColor;
                                            magicEye.Colour = igs.MarkerColour;
                                            magicEye.ReadingSource = igs.Average ? Reading.AVG_SIGNAL_STRENGTH : Reading.SIGNAL_STRENGTH;

                                            magicEye.TopLeft = new PointF(0.5f - (igs.EyeScale / 2f), _fPadY - (_fHeight * 0.75f) + ((fLargest - igs.EyeScale) * 0.5f));
                                            magicEye.Size = new SizeF(igs.EyeScale, igs.EyeScale);
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> img in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.IMAGE))
                                        {
                                            clsImage image = img.Value as clsImage;
                                            if (image == null) continue;

                                            image.FadeOnRx = igs.FadeOnRx;
                                            image.FadeOnTx = igs.FadeOnTx;

                                            image.TopLeft = new PointF(0.5f - (igs.EyeBezelScale / 2f), _fPadY - (_fHeight * 0.75f) + ((fLargest - igs.EyeBezelScale) * 0.5f));
                                            image.Size = new SizeF(igs.EyeBezelScale, igs.EyeBezelScale);
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue;

                                            fc.FadeOnRx = igs.FadeOnRx;
                                            fc.FadeOnTx = igs.FadeOnTx;
                                        }

                                        // recalc bounds for fade overlay cover as these will change as eye scale changes
                                        System.Drawing.RectangleF eyeBounds = getBounds(ig.ID);
                                        if (!eyeBounds.IsEmpty)
                                        {
                                            foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                            {
                                                clsFadeCover fc = fcs.Value as clsFadeCover;
                                                if (fc == null) continue;

                                                fc.TopLeft = new PointF(0.5f - (fLargest / 2f), _fPadY - (_fHeight * 0.75f));
                                                fc.Size = new SizeF(fLargest, fLargest);
                                            }
                                        }
                                    }
                                    break;
                                case MeterType.ANANMM:
                                case MeterType.CROSS:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //lots of ni objects, and images

                                        foreach (KeyValuePair<string, clsMeterItem> needle in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.NEEDLE))
                                        {
                                            clsNeedleItem ni = needle.Value as clsNeedleItem;
                                            if (ni == null) continue;

                                            if (mt == MeterType.CROSS) //TODO ananmm not done yet
                                            {
                                                if (ni.MMIOVariableIndex != -1)
                                                {
                                                    ni.MMIOGuid = igs.GetMMIOGuid(ni.MMIOVariableIndex);
                                                    ni.MMIOVariable = igs.GetMMIOVariable(ni.MMIOVariableIndex);
                                                }
                                                else
                                                {
                                                    ni.MMIOGuid = Guid.Empty;
                                                    ni.MMIOVariable = "--DEFAULT--";
                                                }
                                            }

                                            if (ni.Primary)
                                            {
                                                //primary needle, as has been added first
                                                ni.UpdateInterval = igs.UpdateInterval;
                                                ni.AttackRatio = igs.AttackRatio;
                                                ni.DecayRatio = igs.DecayRatio;
                                                ni.HistoryDuration = igs.HistoryDuration;
                                                ni.IgnoreHistoryDuration = igs.IgnoreHistoryDuration;
                                                ni.HistoryColour = igs.HistoryColor;
                                                ni.ShowHistory = igs.ShowHistory;
                                                ni.FadeOnRx = igs.FadeOnRx;
                                                ni.FadeOnTx = igs.FadeOnTx;
                                                ni.Colour = igs.MarkerColour;
                                                //bi.Style = igs.BarStyle;
                                                //bi.MarkerColour = igs.MarkerColour;
                                                //bi.PeakHold = igs.PeakHold;
                                                //bi.PeakHoldMarkerColour = igs.PeakHoldMarkerColor;
                                                ni.Shadow = igs.Shadow;
                                                if (mt == MeterType.ANANMM) 
                                                {
                                                    if (ni.ReadingSource == Reading.AVG_SIGNAL_STRENGTH || ni.ReadingSource == Reading.SIGNAL_STRENGTH)
                                                    {
                                                        ni.PeakHold = igs.PeakHold;
                                                        ni.PeakHoldMarkerColour = igs.PeakHoldMarkerColor;
                                                        ni.ReadingSource = igs.Average ? Reading.AVG_SIGNAL_STRENGTH : Reading.SIGNAL_STRENGTH;
                                                    }
                                                    else if(ni.ReadingSource == Reading.PWR)
                                                    {
                                                        ni.PeakHold = igs.PeakHold;
                                                        ni.PeakHoldMarkerColour = igs.PeakHoldMarkerColor;
                                                    }
                                                }
                                                ni.MaxPower = igs.MaxPower;
                                            }
                                            else
                                            {
                                                // not the primary needle
                                                ni.UpdateInterval = igs.UpdateInterval;
                                                ni.AttackRatio = igs.AttackRatio;
                                                ni.DecayRatio = igs.DecayRatio;
                                                ni.FadeOnRx = igs.FadeOnRx;
                                                ni.FadeOnTx = igs.FadeOnTx;
                                                //bi.SubMarkerColour = igs.SubMarkerColour;
                                                ni.Shadow = igs.Shadow;

                                                if (mt == MeterType.ANANMM && ni.ReadingSource == Reading.SWR)
                                                {
                                                    ni.ShowHistory = igs.ShowHistory;
                                                    ni.HistoryDuration = igs.HistoryDuration;
                                                    ni.IgnoreHistoryDuration = igs.IgnoreHistoryDuration;
                                                }
                                                else if (mt == MeterType.CROSS && ni.ReadingSource == Reading.REVERSE_PWR)
                                                {
                                                    //ni.Colour = igs.MarkerColour;
                                                    ni.ShowHistory = igs.ShowHistory;
                                                    ni.HistoryDuration = igs.HistoryDuration;
                                                    ni.IgnoreHistoryDuration = igs.IgnoreHistoryDuration;
                                                    ni.MaxPower = igs.MaxPower;
                                                }
                                                ni.Colour = igs.SubMarkerColour;
                                            }
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> img in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.IMAGE))
                                        {
                                            clsImage image = img.Value as clsImage;
                                            if (image == null) continue;

                                            image.FadeOnRx = igs.FadeOnRx;
                                            image.FadeOnTx = igs.FadeOnTx;
                                            if(image.Primary) image.DarkMode = igs.DarkMode;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColour = sc.Value as clsSolidColour;
                                            if (solidColour == null) continue;

                                            solidColour.FadeOnRx = igs.FadeOnRx;
                                            solidColour.FadeOnTx = igs.FadeOnTx;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> nsi in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.NEEDLE_SCALE_PWR))
                                        {
                                            clsNeedleScalePwrItem nspi = nsi.Value as clsNeedleScalePwrItem;
                                            if (nspi == null) continue;

                                            nspi.FadeOnRx = igs.FadeOnRx;
                                            nspi.FadeOnTx = igs.FadeOnTx;
                                            nspi.MaxPower = igs.MaxPower;
                                            nspi.DarkMode = igs.DarkMode;
                                            nspi.LowColour = igs.PowerScaleColour;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> txts in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.TEXT))
                                        {
                                            clsText txt = txts.Value as clsText;
                                            if (txt == null) continue;

                                            txt.FadeOnRx = igs.FadeOnRx;
                                            txt.FadeOnTx = igs.FadeOnTx;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue;

                                            fc.FadeOnRx = igs.FadeOnRx;
                                            fc.FadeOnTx = igs.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.VFO_DISPLAY:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        bRebuild = true;
                                        bool both = false;
                                        float padding = 0;
                                        foreach (KeyValuePair<string, clsMeterItem> vfos in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.VFO_DISPLAY))
                                        {
                                            float pad = 0;
                                            clsVfoDisplay vfo = vfos.Value as clsVfoDisplay;
                                            if (vfo == null) continue;

                                            //vfo.UpdateInterval = igs.UpdateInterval;
                                            vfo.FadeOnRx = igs.FadeOnRx;
                                            vfo.FadeOnTx = igs.FadeOnTx;

                                            vfo.TypeColour = igs.TitleColor;
                                            vfo.FrequencyColour = igs.MarkerColour;
                                            vfo.ModeColour = igs.SubMarkerColour;
                                            vfo.SplitBackColour = igs.LowColor;
                                            vfo.SplitColour = igs.HighColor;
                                            vfo.RxColour = igs.PeakValueColour;
                                            vfo.TxColour = igs.PeakHoldMarkerColor;
                                            vfo.FilterColour = igs.HistoryColor;
                                            vfo.BandColour = igs.SegmentedSolidLowColour;
                                            vfo.DigitHighlightColour = igs.PowerScaleColour;

                                            vfo.ShowBandText = igs.GetSetting<bool>("vfo_showbandtext", false, false, false, false);
                                            if (vfo.ShowBandText) pad = 0.03f;

                                            vfo.BandTextColour = igs.GetSetting<System.Drawing.Color>("vfo_showbandtext_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.LimeGreen);
                                            vfo.FrequencyColourSmall = igs.GetSetting<System.Drawing.Color>("vfo_frequency_small_numbers_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.Orange);

                                            vfo.LockColour = igs.GetSetting<System.Drawing.Color>("vfo_lock_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.LimeGreen);
                                            vfo.SyncColour = igs.GetSetting<System.Drawing.Color>("vfo_sync_colour", false, System.Drawing.Color.Empty, System.Drawing.Color.Empty, System.Drawing.Color.LimeGreen);

                                            vfo.VFODispMode = (clsVfoDisplay.VFODisplayMode)igs.HistoryDuration;
                                            if (vfo.VFODispMode < clsVfoDisplay.VFODisplayMode.VFO_BOTH || vfo.VFODispMode > clsVfoDisplay.VFODisplayMode.VFO_B) vfo.VFODispMode = clsVfoDisplay.VFODisplayMode.VFO_BOTH;
                                            if (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH) both = true;

                                            if (both)
                                            {
                                                vfo.Size = new SizeF(1f - _fPadX * 2f, pad + (_fHeight + _fHeight * 0.75f));
                                                padding += pad;
                                            }
                                            else
                                            {
                                                vfo.Size = new SizeF(1f - _fPadX * 2f, pad * 2f + (_fHeight + _fHeight * 0.75f) * 2f);
                                                padding += pad * 2f;
                                            }
                                        }
                                        
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            solidColor.FadeOnRx = igs.FadeOnRx;
                                            solidColor.FadeOnTx = igs.FadeOnTx;
                                            solidColor.Colour = igs.Colour;

                                            if (!solidColor.Primary)
                                            {
                                                solidColor.Visible = (clsVfoDisplay.VFODisplayMode)igs.HistoryDuration == clsVfoDisplay.VFODisplayMode.VFO_BOTH;

                                                if(both)
                                                    solidColor.TopLeft = new PointF(0.5f + (_fPadX * 0.5f), /*fTop +*/ _fPadY - _fHeight * 0.75f);
                                                else
                                                    solidColor.TopLeft = new PointF(_fPadX, /*fTop +*/ _fPadY - _fHeight * 0.75f);
                                            }

                                            if (both)
                                                solidColor.Size = new SizeF(0.5f - _fPadX - (_fPadX * 0.5f), padding + _fHeight + _fHeight * 0.75f);
                                            else
                                                solidColor.Size = new SizeF(1f - _fPadX * 2f, padding + (_fHeight + _fHeight * 0.75f) * 2f);

                                            if (solidColor.Primary)
                                                ig.Size = new SizeF(solidColor.Size.Width + (both ? solidColor.Size.Width : 0f) + _fPadX, solidColor.TopLeft.Y + solidColor.Size.Height);
                                        }
                                    }
                                    break;
                                case MeterType.CLOCK:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);

                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            solidColor.FadeOnRx = igs.FadeOnRx;
                                            solidColor.FadeOnTx = igs.FadeOnTx;
                                            solidColor.Colour = igs.Colour;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> clks in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.CLOCK))
                                        {
                                            clsClock clk = clks.Value as clsClock;
                                            if (clk == null) continue;

                                            //vfo.UpdateInterval = igs.UpdateInterval;
                                            //clk.Show24HourCLock = igs.Clock24Hr;
                                            clk.FadeOnRx = igs.FadeOnRx;
                                            clk.FadeOnTx = igs.FadeOnTx;
                                            clk.TimeColour = igs.MarkerColour;
                                            clk.DateColour = igs.SubMarkerColour;
                                            clk.ShowType = igs.ShowType;
                                            clk.TypeTitleColour = igs.TitleColor;
                                            clk.Show24HourCLock = igs.ShowMarker; // showmarker is used for the bool
                                        }
                                    }
                                    break;
                                case MeterType.SIGNAL_TEXT:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);

                                        foreach (KeyValuePair<string, clsMeterItem> stds in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SIGNAL_TEXT_DISPLAY))
                                        {
                                            clsSignalText cst = stds.Value as clsSignalText;
                                            if (cst == null) continue;

                                            if (cst.MMIOVariableIndex != -1)
                                            {
                                                cst.MMIOGuid = igs.GetMMIOGuid(cst.MMIOVariableIndex);
                                                cst.MMIOVariable = igs.GetMMIOVariable(cst.MMIOVariableIndex);
                                            }
                                            else
                                            {
                                                cst.MMIOGuid = Guid.Empty;
                                                cst.MMIOVariable = "--DEFAULT--";
                                            }

                                            cst.UpdateInterval = igs.UpdateInterval;
                                            cst.AttackRatio = igs.AttackRatio;
                                            cst.DecayRatio = igs.DecayRatio;
                                            cst.HistoryDuration = igs.HistoryDuration;
                                            cst.IgnoreHistoryDuration = igs.IgnoreHistoryDuration;
                                            cst.FadeOnRx = igs.FadeOnRx;
                                            cst.FadeOnTx = igs.FadeOnTx;
                                            //cst.Colour = igs.Colour;
                                            cst.FontColour = igs.MarkerColour;
                                            cst.HistoryColour = igs.SubMarkerColour;
                                            cst.PeakValueColour = igs.PeakValueColour;
                                            cst.ShowPeakValue = igs.PeakValue;
                                            cst.ReadingSource = igs.Average ? Reading.AVG_SIGNAL_STRENGTH : Reading.SIGNAL_STRENGTH;
                                            cst.ShowSubMarker = igs.ShowSubMarker;

                                            ig.TopLeft = new PointF(ig.TopLeft.X, _fPadY - (_fHeight * 0.75f));
                                            if (cst.ShowPeakValue)
                                                ig.Size = new SizeF(ig.Size.Width, ig.TopLeft.Y + (_fHeight + _fHeight * 0.75f) * 1.5f);
                                            else
                                                ig.Size = new SizeF(ig.Size.Width, ig.TopLeft.Y + (_fHeight + _fHeight * 0.75f) * 1.1f);

                                            bRebuild = true;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            solidColor.FadeOnRx = igs.FadeOnRx;
                                            solidColor.FadeOnTx = igs.FadeOnTx;
                                            solidColor.Colour = igs.Colour;

                                            solidColor.Size = new SizeF(solidColor.Size.Width, ig.Size.Height - ig.TopLeft.Y);
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue;

                                            fc.FadeOnRx = igs.FadeOnRx;
                                            fc.FadeOnTx = igs.FadeOnTx;
                                        }
                                    }
                                    break;
                                default:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        // a bar can be up to 2 hbars (peak/av), back colour, and scale

                                        foreach (KeyValuePair<string, clsMeterItem> hbar in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.H_BAR))
                                        {
                                            clsBarItem bi = hbar.Value as clsBarItem;
                                            if (bi == null) continue;

                                            //
                                            //for (int i = 0; i < 10; i++)
                                            //{
                                            //    bi.SetMMIOGuid(i, igs.GetMMIOGuid(i));
                                            //    bi.SetMMIOVariable(i, igs.GetMMIOVariable(i));
                                            //}
                                            if (bi.MMIOVariableIndex != -1)
                                            {
                                                bi.MMIOGuid = igs.GetMMIOGuid(bi.MMIOVariableIndex);
                                                bi.MMIOVariable = igs.GetMMIOVariable(bi.MMIOVariableIndex);
                                            }
                                            else
                                            {
                                                bi.MMIOGuid = Guid.Empty;
                                                bi.MMIOVariable = "--DEFAULT--";
                                            }
                                            //

                                            if (bi.Primary)
                                            {
                                                //primary bar, as has been added first
                                                bi.UpdateInterval = igs.UpdateInterval;
                                                bi.AttackRatio = igs.AttackRatio;
                                                bi.DecayRatio = igs.DecayRatio;
                                                bi.FadeOnRx = igs.FadeOnRx;
                                                bi.FadeOnTx = igs.FadeOnTx;
                                                bi.HistoryDuration = igs.HistoryDuration;
                                                bi.IgnoreHistoryDuration = igs.IgnoreHistoryDuration;
                                                bi.HistoryColour = igs.HistoryColor;
                                                bi.ShowHistory = igs.ShowHistory;
                                                bi.Style = igs.BarStyle;
                                                bi.MarkerColour = igs.MarkerColour;
                                                bi.ShowMarker = igs.ShowMarker;
                                                bi.PeakHold = igs.PeakHold;
                                                bi.PeakHoldMarkerColour = igs.PeakHoldMarkerColor;
                                                bi.Colour = igs.SegmentedSolidLowColour;
                                                bi.ColourHigh = igs.SegmentedSolidHighColour;
                                                bi.ShowPeakValue = igs.PeakValue;
                                                bi.PeakValueColour = igs.PeakValueColour;
                                                bi.Unit = igs.Unit;                                                
                                            }
                                            else
                                            {
                                                bi.UpdateInterval = igs.UpdateInterval;
                                                bi.AttackRatio = igs.AttackRatio;
                                                bi.DecayRatio = igs.DecayRatio;
                                                bi.FadeOnRx = igs.FadeOnRx;
                                                bi.FadeOnTx = igs.FadeOnTx;
                                                bi.MarkerColour = igs.SubMarkerColour;
                                                bi.ShowMarker = igs.ShowSubMarker;
                                                //igs.SubMarkerColour = bi.SubMarkerColour;
                                            }
                                            if (bi.ReadingSource == Reading.PWR || bi.ReadingSource == Reading.REVERSE_PWR) bi.MaxPower = igs.MaxPower;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            solidColor.FadeOnRx = igs.FadeOnRx;
                                            solidColor.FadeOnTx = igs.FadeOnTx;
                                            solidColor.Colour = igs.Colour;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> si in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.H_SCALE))
                                        {
                                            clsScaleItem scaleItem = si.Value as clsScaleItem;
                                            if (scaleItem == null) continue;

                                            scaleItem.FadeOnRx = igs.FadeOnRx;
                                            scaleItem.FadeOnTx = igs.FadeOnTx;
                                            scaleItem.LowColour = igs.LowColor;
                                            scaleItem.HighColour = igs.HighColor;
                                            scaleItem.ShowType = igs.ShowType;
                                            scaleItem.FontColourType = igs.TitleColor;
                                            scaleItem.FontColourLow = igs.LowColor;
                                            scaleItem.FontColourHigh = igs.HighColor;
                                            if(scaleItem.ReadingSource == Reading.PWR || scaleItem.ReadingSource == Reading.REVERSE_PWR) scaleItem.MaxPower = igs.MaxPower;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue;

                                            fc.FadeOnRx = igs.FadeOnRx;
                                            fc.FadeOnTx = igs.FadeOnTx;
                                        }
                                    }
                                    break;
                            }

                            if (bRebuild)
                            {
                                Rebuild();
                            }
                            else
                            {
                                UpdateIntervals();
                            }

                            return;
                        }
                    }

                    return;
                }
            }
            public clsIGSettings GetSettingsForMeterGroup(MeterType mt, int order = -1)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return null;

                    Dictionary<string, clsMeterItem> itemGroups = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in itemGroups)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null && ig.MeterType == mt && (order == -1 || order == ig.Order))
                        {
                            clsIGSettings igs = new clsIGSettings();
                            switch (mt)
                            {
                                case MeterType.HISTORY:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.HISTORY))
                                        {
                                            clsHistoryItem his = me.Value as clsHistoryItem;
                                            if (his == null) continue; // skip

                                            igs.SetSetting<float>("history_vertical_ratio", his.VerticalRatio);
                                            igs.SetSetting<System.Drawing.Color>("history_background_colour", his.BackColour);
                                            igs.SetSetting<float>("history_update", (float)his.UpdateInterval);
                                            igs.SetSetting<float>("history_keep_for", (float)his.KeepFor);

                                            igs.SetSetting<Reading>("history_reading_0", his.Reading0);
                                            igs.SetSetting<Reading>("history_reading_1", his.Reading1);

                                            igs.SetSetting<bool>("history_auto_scale_0", his.AutoScale0);
                                            igs.SetSetting<float>("history_min_0", his.Min0Manual);
                                            igs.SetSetting<float>("history_max_0", his.Max0Manual);

                                            igs.SetSetting<bool>("history_show_scale_1", his.ShowScale1);
                                            igs.SetSetting<bool>("history_auto_scale_1", his.AutoScale1);
                                            igs.SetSetting<float>("history_min_1", his.Min1Manual);
                                            igs.SetSetting<float>("history_max_1", his.Max1Manual);

                                            igs.SetMMIOGuid(0, his.Axis0MMIOGuid);
                                            igs.SetMMIOVariable(0, his.Axis0MMIOVariable);
                                            igs.SetMMIOGuid(1, his.Axis1MMIOGuid);
                                            igs.SetMMIOVariable(1, his.Axis1MMIOVariable);

                                            igs.SetSetting<System.Drawing.Color>("history_colour_0", his.Axis0Colour);
                                            igs.SetSetting<System.Drawing.Color>("history_colour_1", his.Axis1Colour);

                                            igs.SetSetting<System.Drawing.Color>("history_colour_lines", his.LinesColour);
                                            igs.SetSetting<System.Drawing.Color>("history_colour_time", his.TimeColour);

                                            igs.FadeOnRx = his.FadeOnRx;
                                            igs.FadeOnTx = his.FadeOnTx;
                                        }

                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.TUNESTEP_BUTTONS:
                                case MeterType.ANTENNA_BUTTONS:
                                case MeterType.FILTER_BUTTONS:
                                case MeterType.MODE_BUTTONS:
                                case MeterType.BAND_BUTTONS:
                                case MeterType.DISCORD_BUTTONS:
                                    {
                                        clsMeterItem.MeterItemType mit = clsMeterItem.MeterItemType.BASE;
                                        switch (mt)
                                        {
                                            case MeterType.TUNESTEP_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.TUNESTEP_BUTTONS;
                                                break;
                                            case MeterType.ANTENNA_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.ANTENNA_BUTTONS;
                                                break;
                                            case MeterType.FILTER_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.FILTER_BUTTONS;
                                                break;
                                            case MeterType.MODE_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.MODE_BUTTONS;
                                                break;
                                            case MeterType.BAND_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.BAND_BUTTONS;
                                                break;
                                            case MeterType.DISCORD_BUTTONS:
                                                mit = clsMeterItem.MeterItemType.DISCORD_BUTTONS;
                                                break;
                                        }
                                        if (mit == clsMeterItem.MeterItemType.BASE) continue; // skip

                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == mit))
                                        {
                                            clsButtonBox bb = me.Value as clsButtonBox;
                                            if (bb == null) continue; // skip

                                            // just use button 0 as all are the same for these buttonboxes

                                            igs.SetSetting<bool>("buttonbox_use_indicator", bb.GetUseIndicator(0, 0));
                                            igs.SetSetting<float>("buttonbox_indicator_border", bb.GetIndicatorWidth(0, 0) * 10f);
                                            igs.SetSetting<System.Drawing.Color>("buttonbox_on_colour", bb.GetOnColour(0, 0));
                                            igs.SetSetting<System.Drawing.Color>("buttonbox_off_colour", bb.GetOffColour(0, 0));

                                            igs.SetSetting<System.Drawing.Color>("buttonbox_fill_colour", bb.GetFillColour(0, 0));
                                            igs.SetSetting<System.Drawing.Color>("buttonbox_hover_colour", bb.GetHoverColour(0, 0));
                                            igs.SetSetting<System.Drawing.Color>("buttonbox_border_colour", bb.GetBorderColour(0, 0));

                                            igs.SetSetting<System.Drawing.Color>("buttonbox_click_colour", bb.GetClickColour(0, 0));
                                            igs.SetSetting<System.Drawing.Color>("buttonbox_font_colour", bb.GetFontColour(0, 0));

                                            igs.SetSetting<int>("buttonbox_columns", bb.Columns);
                                            igs.SetSetting<float>("buttonbox_border", bb.Border * 10f);
                                            igs.SetSetting<float>("buttonbox_margin", bb.Margin * 10f);
                                            igs.SetSetting<float>("buttonbox_radius", bb.Radius * 10f);
                                            igs.SetSetting<float>("buttonbox_height_ratio", bb.HeightRatio);

                                            igs.SetSetting<bool>("buttonbox_use_off_colour", bb.GetUseOffColour(0, 0));

                                            igs.SetSetting<clsButtonBox.IndicatorType>("buttonbox_indicator_type", bb.GetIndicatorType(0, 0));

                                            igs.SetSetting<float>("buttonbox_font_scale", bb.FontScale);
                                            igs.SetSetting<float>("buttonbox_font_shift_x", bb.FontShiftX);
                                            igs.SetSetting<float>("buttonbox_font_shift_y", bb.FontShiftY);

                                            if(mt == MeterType.TUNESTEP_BUTTONS)
                                            {
                                                igs.SetSetting<int>("buttonbox_tunestep_bitfield", bb.VisibleBits);
                                            }
                                            else if (mt == MeterType.ANTENNA_BUTTONS)
                                            {
                                                int bits = bb.VisibleBits;
                                                igs.SetSetting<bool>("buttonbox_rx1", (bits & (1 << 0)) != 0);
                                                igs.SetSetting<bool>("buttonbox_rx2", (bits & (1 << 1)) != 0);
                                                igs.SetSetting<bool>("buttonbox_rx3", (bits & (1 << 2)) != 0);
                                                igs.SetSetting<bool>("buttonbox_byp", (bits & (1 << 3)) != 0);
                                                igs.SetSetting<bool>("buttonbox_ext1", (bits & (1 << 4)) != 0);
                                                igs.SetSetting<bool>("buttonbox_xvtr", (bits & (1 << 5)) != 0);
                                                igs.SetSetting<bool>("buttonbox_tx1", (bits & (1 << 6)) != 0);
                                                igs.SetSetting<bool>("buttonbox_tx2", (bits & (1 << 7)) != 0);
                                                igs.SetSetting<bool>("buttonbox_tx3", (bits & (1 << 8)) != 0);
                                                igs.SetSetting<bool>("buttonbox_rxtxant", (bits & (1 << 9)) != 0);
                                            }

                                            igs.FontFamily1 = bb.GetFontFamily(0, 0);
                                            igs.FontSize1 = bb.GetFontSize(0, 0);
                                            igs.FontStyle1 = bb.GetFontStyle(0, 0);

                                            igs.FadeOnRx = bb.FadeOnRx;
                                            igs.FadeOnTx = bb.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.ROTATOR:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ROTATOR))
                                        {
                                            clsRotatorItem rotator = me.Value as clsRotatorItem;
                                            if (rotator == null) continue; // skip

                                            if (rotator.MMIOVariableIndex != -1)
                                            {
                                                igs.SetMMIOGuid(rotator.MMIOVariableIndex, rotator.MMIOGuid);
                                                igs.SetMMIOVariable(rotator.MMIOVariableIndex, rotator.MMIOVariable);
                                            }

                                            if (rotator.Primary)
                                            {
                                                igs.UpdateInterval = rotator.UpdateInterval;
                                                igs.TitleColor = rotator.ArrowColour;
                                                igs.MarkerColour = rotator.BigBlobColour;
                                                igs.SubMarkerColour = rotator.SmallBlobColour;
                                                igs.ShowMarker = rotator.ShowBeamWidth;
                                                igs.LowColor = rotator.BeamWidthColour;
                                                igs.HighColor = rotator.OuterTextColour;
                                                igs.ShowHistory = rotator.ShowCardinals;
                                                igs.FadeOnRx = rotator.FadeOnRx;
                                                igs.FadeOnTx = rotator.FadeOnTx;
                                                igs.AttackRatio = rotator.BeamWidth;
                                                igs.EyeScale = rotator.Padding;
                                                igs.HistoryDuration = (int)rotator.ViewMode;
                                                igs.SetMMIOGuid(2, rotator.DataOutMMIOGuid);
                                                igs.ShowType = rotator.AllowControl;
                                                igs.HistoryColor = rotator.ControlColour;
                                                igs.Text1 = rotator.AZControlString;
                                                igs.Text2 = rotator.ELEControlString;
                                                igs.FontFamily1 = rotator.STOPControlString;
                                                igs.SetSetting<float>("rotator_beamwidth_alpha", rotator.BeamWidthAlpha);
                                            }
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidcolor = sc.Value as clsSolidColour;
                                            if (solidcolor == null) continue; // skip the sc

                                            igs.Colour = solidcolor.Colour;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> img in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.IMAGE))
                                        {
                                            clsImage image = img.Value as clsImage;
                                            if (image == null) continue; // skip

                                            igs.DarkMode = image.DarkMode;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.LED:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.LED))
                                        {
                                            clsLed led = me.Value as clsLed;
                                            if (led == null) continue; // skip

                                            igs.FadeOnRx = led.FadeOnRx;
                                            igs.FadeOnTx = led.FadeOnTx;
                                            igs.Colour = led.TrueColour;
                                            igs.MarkerColour = led.FalseColour;

                                            igs.TitleColor = led.PanelBackColour1;
                                            igs.HistoryColor = led.PanelBackColour2;
                                            igs.ShowSubMarker = led.ShowBackPanel;

                                            igs.EyeScale = led.OffsetX;
                                            igs.EyeBezelScale = led.OffsetY;
                                            igs.AttackRatio = led.SizeX;
                                            igs.DecayRatio = led.SizeY;

                                            igs.UpdateInterval = led.UpdateInterval;

                                            igs.Text1 = led.Condition;

                                            igs.SpacerPadding = led.Padding;

                                            igs.ShowHistory = led.ScriptError;
                                            igs.ShowType = led.ScriptValid;

                                            igs.PeakHold = led.ShowTrue;
                                            igs.ShowMarker = led.ShowFalse;
                                            if (!led.Blink && !led.Pulsate)
                                                igs.IgnoreHistoryDuration = 0;
                                            else if (led.Blink && !led.Pulsate)
                                                igs.IgnoreHistoryDuration = 1;
                                            else if (!led.Blink && led.Pulsate)
                                                igs.IgnoreHistoryDuration = 2;

                                            igs.SetSetting<bool>("led_notx_true", led.NoTxTrue);
                                            igs.SetSetting<bool>("led_notx_false", led.NoTxFalse);
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.TEXT_OVERLAY:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.TEXT_OVERLAY))
                                        {
                                            clsTextOverlay text_overlay = me.Value as clsTextOverlay;
                                            if (text_overlay == null) continue; // skip

                                            igs.FadeOnRx = text_overlay.FadeOnRx;
                                            igs.FadeOnTx = text_overlay.FadeOnTx;
                                            igs.Colour = text_overlay.TextColour1;
                                            igs.MarkerColour = text_overlay.TextColour2;
                                            igs.SubMarkerColour = text_overlay.TextBackColour1;
                                            igs.ShowMarker = text_overlay.ShowTextBackColour1;
                                            igs.PeakValueColour = text_overlay.TextBackColour2;
                                            igs.ShowType = text_overlay.ShowTextBackColour2;

                                            igs.TitleColor = text_overlay.PanelBackColour1;
                                            igs.HistoryColor = text_overlay.PanelBackColour2;
                                            igs.ShowSubMarker = text_overlay.ShowBackPanel;

                                            igs.EyeScale = text_overlay.TextXOffset1;
                                            igs.EyeBezelScale = text_overlay.TextYOffset1;
                                            igs.AttackRatio = text_overlay.TextXOffset2;
                                            igs.DecayRatio = text_overlay.TextYOffset2;

                                            igs.Text1 = text_overlay.Text1;
                                            igs.Text2 = text_overlay.Text2;

                                            igs.FontFamily1 = text_overlay.FontFamily1;
                                            igs.FontStyle1 = text_overlay.Style1;
                                            igs.FontSize1 = text_overlay.FontSize1;
                                            igs.FontFamily2 = text_overlay.FontFamily2;
                                            igs.FontStyle2 = text_overlay.Style2;
                                            igs.FontSize2 = text_overlay.FontSize2;

                                            igs.SpacerPadding = text_overlay.Padding;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.DATA_OUT:
                                    { 
                                    Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.DATA_OUT))
                                        {
                                            clsDataOut data_out = me.Value as clsDataOut;
                                            if (data_out == null) continue; // skip

                                            igs.SetMMIOGuid(0, data_out.MMIOGuid);
                                            igs.UpdateInterval = data_out.UpdateInterval;
                                        }  
                                    }
                                    break;
                                case MeterType.WEB_IMAGE:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.WEB_IMAGE))
                                        {
                                            clsWebImage webimg = me.Value as clsWebImage;
                                            if (webimg == null) continue; // skip

                                            igs.FadeOnRx = webimg.FadeOnRx;
                                            igs.FadeOnTx = webimg.FadeOnTx;
                                            igs.Text1 = webimg.URL;
                                            igs.UpdateInterval = webimg.SecondsInterval;
                                            igs.DarkMode = webimg.BypassCache;
                                            igs.EyeScale = webimg.WidthScale;
                                            igs.HistoryDuration = (int)webimg.WebImageState;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.SPACER:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SPACER))
                                        {
                                            clsSpacerItem spacer = me.Value as clsSpacerItem;
                                            if (spacer == null) continue; // skip

                                            igs.Colour = spacer.Colour1;
                                            igs.MarkerColour = spacer.Colour2;
                                            igs.SpacerPadding = spacer.Padding;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.MAGIC_EYE:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //one image, and the me
                                        foreach (KeyValuePair<string, clsMeterItem> me in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.MAGIC_EYE))
                                        {
                                            clsMagicEyeItem magicEye = me.Value as clsMagicEyeItem;
                                            if (magicEye == null) continue; // skip the img

                                            if (magicEye.MMIOVariableIndex != -1)
                                            {
                                                igs.SetMMIOGuid(magicEye.MMIOVariableIndex, magicEye.MMIOGuid);
                                                igs.SetMMIOVariable(magicEye.MMIOVariableIndex, magicEye.MMIOVariable);
                                            }

                                            igs.UpdateInterval = magicEye.UpdateInterval;
                                            igs.AttackRatio = magicEye.AttackRatio;
                                            igs.DecayRatio = magicEye.DecayRatio;
                                            igs.HistoryDuration = magicEye.HistoryDuration;
                                            igs.IgnoreHistoryDuration = magicEye.IgnoreHistoryDuration;
                                            igs.HistoryColor = magicEye.HistoryColour;
                                            igs.ShowHistory = magicEye.ShowHistory;
                                            //igs.FadeOnRx = magicEye.FadeOnRx;
                                            //igs.FadeOnTx = magicEye.FadeOnTx;
                                            //igs.BarStyle = bi.Style;
                                            //igs.MarkerColour = bi.MarkerColour;
                                            //igs.PeakHold = bi.PeakHold;
                                            //igs.PeakHoldMarkerColor = bi.PeakHoldMarkerColour;
                                            igs.MarkerColour = magicEye.Colour;
                                            igs.EyeScale = magicEye.Size.Height;
                                            
                                            igs.Average = magicEye.ReadingSource == Reading.AVG_SIGNAL_STRENGTH;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> img in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.IMAGE))
                                        {
                                            clsImage image = img.Value as clsImage;
                                            if (image == null) continue; // skip the img

                                            igs.EyeBezelScale = image.Size.Width;
                                            //igs.FadeOnRx = image.FadeOnRx;
                                            //igs.FadeOnTx = image.FadeOnTx;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.ANANMM:
                                case MeterType.CROSS:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        //lots of ni objects, and images

                                        foreach (KeyValuePair<string, clsMeterItem> needle in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.NEEDLE))
                                        {
                                            clsNeedleItem ni = needle.Value as clsNeedleItem;
                                            if (ni == null) continue; // skip the images

                                            if (mt == MeterType.CROSS) //TODO ananmm not done yet
                                            {
                                                if (ni.MMIOVariableIndex != -1)
                                                {
                                                    igs.SetMMIOGuid(ni.MMIOVariableIndex, ni.MMIOGuid);
                                                    igs.SetMMIOVariable(ni.MMIOVariableIndex, ni.MMIOVariable);
                                                }
                                            }

                                            if (ni.Primary)
                                            {
                                                //primary needle, as has been added first
                                                igs.UpdateInterval = ni.UpdateInterval;
                                                igs.AttackRatio = ni.AttackRatio;
                                                igs.DecayRatio = ni.DecayRatio;
                                                igs.HistoryDuration = ni.HistoryDuration;
                                                igs.IgnoreHistoryDuration = ni.IgnoreHistoryDuration;
                                                igs.HistoryColor = ni.HistoryColour;
                                                igs.ShowHistory = ni.ShowHistory;
                                                //igs.FadeOnRx = ni.FadeOnRx;
                                                //igs.FadeOnTx = ni.FadeOnTx;
                                                //igs.BarStyle = bi.Style;
                                                //igs.MarkerColour = bi.MarkerColour;
                                                //igs.PeakHold = bi.PeakHold;
                                                //igs.PeakHoldMarkerColor = bi.PeakHoldMarkerColour;
                                                igs.Shadow = ni.Shadow;
                                                igs.MarkerColour = ni.Colour;
                                                if (mt == MeterType.ANANMM) {
                                                    if (ni.ReadingSource == Reading.SIGNAL_STRENGTH || ni.ReadingSource == Reading.AVG_SIGNAL_STRENGTH)
                                                    {
                                                        igs.PeakHold = ni.PeakHold;
                                                        igs.PeakHoldMarkerColor = ni.PeakHoldMarkerColour;
                                                        igs.Average = ni.ReadingSource == Reading.AVG_SIGNAL_STRENGTH;
                                                    }
                                                }
                                                igs.MaxPower = ni.MaxPower;
                                            }
                                            else
                                            {
                                                //igs.SubMarkerColour = bi.SubMarkerColour;
                                                //if(mt == MeterType.CROSS) igs.MarkerColour = ni.Colour;
                                                igs.SubMarkerColour = ni.Colour;
                                                igs.SubIndicators = true;
                                                //igs.ShowSubMarker = ni
                                            }
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> img in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.IMAGE))
                                        {
                                            clsImage image = img.Value as clsImage;
                                            if (image == null) continue; // skip the img

                                            //igs.FadeOnRx = image.FadeOnRx;
                                            //igs.FadeOnTx = image.FadeOnTx;
                                            if (image.Primary) igs.DarkMode = image.DarkMode;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.NEEDLE_SCALE_PWR))
                                        {
                                            clsNeedleScalePwrItem nspi = sc.Value as clsNeedleScalePwrItem;
                                            if (nspi == null) continue;

                                            //nspi.FadeOnRx = igs.FadeOnRx;
                                            //nspi.FadeOnTx = igs.FadeOnTx;
                                            if (nspi.Primary)
                                            {
                                                igs.MaxPower = nspi.MaxPower;
                                                igs.PowerScaleColour = nspi.LowColour;
                                            }
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                                case MeterType.VFO_DISPLAY:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);

                                        foreach (KeyValuePair<string, clsMeterItem> vfos in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.VFO_DISPLAY))
                                        {
                                            clsVfoDisplay vfo = vfos.Value as clsVfoDisplay;
                                            if (vfo == null) continue;

                                            igs.FadeOnRx = vfo.FadeOnRx;
                                            igs.FadeOnTx = vfo.FadeOnTx;

                                            igs.TitleColor = vfo.TypeColour;
                                            igs.MarkerColour = vfo.FrequencyColour;
                                            igs.SubMarkerColour = vfo.ModeColour;
                                            igs.LowColor = vfo.SplitBackColour;
                                            igs.HighColor = vfo.SplitColour;
                                            igs.PeakValueColour = vfo.RxColour;
                                            igs.PeakHoldMarkerColor = vfo.TxColour;
                                            igs.HistoryColor = vfo.FilterColour;
                                            igs.SegmentedSolidLowColour = vfo.BandColour;
                                            igs.PowerScaleColour = vfo.DigitHighlightColour;

                                            igs.HistoryDuration = (int)vfo.VFODispMode;

                                            igs.SetSetting<bool>("vfo_showbandtext", vfo.ShowBandText);
                                            igs.SetSetting<System.Drawing.Color>("vfo_showbandtext_colour", vfo.BandTextColour);
                                            igs.SetSetting<System.Drawing.Color>("vfo_frequency_small_numbers_colour", vfo.FrequencyColourSmall);

                                            igs.SetSetting<System.Drawing.Color>("vfo_lock_colour", vfo.LockColour);
                                            igs.SetSetting<System.Drawing.Color>("vfo_sync_colour", vfo.SyncColour);
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            //igs.FadeOnRx = solidColor.FadeOnRx;
                                            //solidColor.FadeOnTx = igs.FadeOnTx;
                                            igs.Colour = solidColor.Colour;
                                        }
                                    }
                                    break;
                                case MeterType.CLOCK:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);

                                        foreach (KeyValuePair<string, clsMeterItem> vfos in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.CLOCK))
                                        {
                                            clsClock clk = vfos.Value as clsClock;
                                            if (clk == null) continue;

                                            //igs.UpdateInterval = vfo.UpdateInterval;
                                            igs.FadeOnRx = clk.FadeOnRx;
                                            igs.FadeOnTx = clk.FadeOnTx;

                                            //igs.Clock24Hr = clk.Show24HourCLock;
                                            igs.MarkerColour = clk.TimeColour;
                                            igs.SubMarkerColour = clk.DateColour;
                                            igs.ShowType = clk.ShowType;
                                            igs.TitleColor = clk.TypeTitleColour;
                                            igs.ShowMarker = clk.Show24HourCLock; // showmarker is used for the bool
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            //igs.FadeOnRx = solidColor.FadeOnRx;
                                            //solidColor.FadeOnTx = igs.FadeOnTx;
                                            igs.Colour = solidColor.Colour;
                                        }
                                    }
                                    break;
                                case MeterType.SIGNAL_TEXT:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);

                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidColor = sc.Value as clsSolidColour;
                                            if (solidColor == null) continue;

                                            //igs.FadeOnRx = solidColor.FadeOnRx;
                                            //igs.FadeOnTx = solidColor.FadeOnTx;
                                            igs.Colour = solidColor.Colour;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> stds in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SIGNAL_TEXT_DISPLAY))
                                        {
                                            clsSignalText cst = stds.Value as clsSignalText;
                                            if (cst == null) continue;

                                            if (cst.MMIOVariableIndex != -1)
                                            {
                                                igs.SetMMIOGuid(cst.MMIOVariableIndex, cst.MMIOGuid);
                                                igs.SetMMIOVariable(cst.MMIOVariableIndex, cst.MMIOVariable);
                                            }

                                            igs.UpdateInterval = cst.UpdateInterval;
                                            igs.AttackRatio = cst.AttackRatio;
                                            igs.DecayRatio = cst.DecayRatio;
                                            igs.HistoryDuration = cst.HistoryDuration;
                                            igs.IgnoreHistoryDuration = cst.IgnoreHistoryDuration;
                                            //igs.FadeOnRx = cst.FadeOnRx;
                                            //igs.FadeOnTx = cst.FadeOnTx;
                                            //igs.Colour = cst.Colour;
                                            igs.MarkerColour = cst.FontColour;
                                            igs.SubMarkerColour = cst.HistoryColour;
                                            igs.PeakValueColour = cst.PeakValueColour;
                                            igs.PeakValue = cst.ShowPeakValue;
                                            igs.Average = cst.ReadingSource == Reading.AVG_SIGNAL_STRENGTH;
                                            igs.ShowSubMarker = cst.ShowSubMarker;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;                                
                                default:
                                    {
                                        Dictionary<string, clsMeterItem> items = itemsFromID(ig.ID, false);
                                        // a bar can be up to 2 hbars (peak/av), back colour, and scale

                                        foreach(KeyValuePair<string, clsMeterItem> hbar in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.H_BAR))
                                        {
                                            clsBarItem bi = hbar.Value as clsBarItem;
                                            if (bi == null) continue;

                                            //
                                            //for (int i = 0; i < 10; i++)
                                            //{
                                            //    igs.SetMMIOGuid(i, bi.GetMMIOGuid(i));
                                            //    igs.SetMMIOVariable(i, bi.GetMMIOVariable(i));
                                            //}
                                            if (bi.MMIOVariableIndex != -1)
                                            {
                                                igs.SetMMIOGuid(bi.MMIOVariableIndex, bi.MMIOGuid);
                                                igs.SetMMIOVariable(bi.MMIOVariableIndex, bi.MMIOVariable);
                                            }
                                            //

                                            if (bi.Primary)
                                            {
                                                //primary bar, as has been added first
                                                igs.UpdateInterval = bi.UpdateInterval;
                                                igs.AttackRatio = bi.AttackRatio;
                                                igs.DecayRatio = bi.DecayRatio;
                                                igs.HistoryDuration = bi.HistoryDuration;
                                                igs.IgnoreHistoryDuration = bi.IgnoreHistoryDuration;
                                                igs.HistoryColor = bi.HistoryColour;
                                                igs.ShowHistory = bi.ShowHistory;
                                                //igs.FadeOnRx = bi.FadeOnRx;
                                                //igs.FadeOnTx = bi.FadeOnTx;
                                                igs.BarStyle = bi.Style;
                                                igs.MarkerColour = bi.MarkerColour;
                                                igs.ShowMarker = bi.ShowMarker;
                                                igs.PeakHold = bi.PeakHold;
                                                igs.PeakHoldMarkerColor = bi.PeakHoldMarkerColour;
                                                igs.SegmentedSolidLowColour = bi.Colour;
                                                igs.SegmentedSolidHighColour = bi.ColourHigh;
                                                igs.PeakValue = bi.ShowPeakValue;
                                                igs.PeakValueColour = bi.PeakValueColour;
                                                igs.Unit = bi.Unit;
                                                if (bi.ReadingSource == Reading.PWR || bi.ReadingSource == Reading.REVERSE_PWR) igs.MaxPower = bi.MaxPower;
                                            }
                                            else
                                            {
                                                //igs.FadeOnRx = bi.FadeOnRx;
                                                //igs.FadeOnTx = bi.FadeOnTx;
                                                //igs.SubMarkerColour = bi.SubMarkerColour;
                                                //if (bi.ReadingSource == Reading.PWR || bi.ReadingSource == Reading.REVERSE_PWR) igs.PowerLimit = bi.MaxPower;
                                                igs.SubMarkerColour = bi.MarkerColour;
                                                igs.ShowSubMarker = bi.ShowMarker;
                                                igs.SubIndicators = true;
                                            }                                            
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> sc in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.SOLID_COLOUR))
                                        {
                                            clsSolidColour solidcolor = sc.Value as clsSolidColour;
                                            if (solidcolor == null) continue; // skip the sc

                                            igs.Colour = solidcolor.Colour;
                                            //igs.FadeOnRx = solidcolor.FadeOnRx;
                                            //igs.FadeOnTx = solidcolor.FadeOnTx;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> si in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.H_SCALE))
                                        {
                                            clsScaleItem scaleItem = si.Value as clsScaleItem;
                                            if (scaleItem == null) continue;

                                            //igs.FadeOnRx = scaleItem.FadeOnRx;
                                            //igs.FadeOnTx = scaleItem.FadeOnTx;
                                            igs.LowColor = scaleItem.LowColour;
                                            igs.HighColor = scaleItem.HighColour;
                                            //igs.show = scaleItem.ShowType; 
                                            igs.TitleColor = scaleItem.FontColourType;
                                            igs.ShowType = scaleItem.ShowType;
                                            if (scaleItem.ReadingSource == Reading.PWR || scaleItem.ReadingSource == Reading.REVERSE_PWR) igs.MaxPower = scaleItem.MaxPower;
                                        }
                                        foreach (KeyValuePair<string, clsMeterItem> fcs in items.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.FADE_COVER))
                                        {
                                            clsFadeCover fc = fcs.Value as clsFadeCover;
                                            if (fc == null) continue; // skip

                                            igs.FadeOnRx = fc.FadeOnRx;
                                            igs.FadeOnTx = fc.FadeOnTx;
                                        }
                                    }
                                    break;
                            }

                            return igs;
                        }
                    }

                    return null;
                }
            }
            public List<int> GetOrderForMeterType(MeterType mt)
            {
                lock (_meterItemsLock)
                {
                    List<int> orders = new List<int>();

                    if (_meterItems == null) return orders;

                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null && ig.MeterType == mt) orders.Add(ig.Order);
                    }

                    return orders;
                }
            }
            public void SetOrderForMeterType(MeterType mt, int nOrder, bool bRebuild, bool bUp, int order = -1)
            {
                // only works for up/down 1 place
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return;

                    if (nOrder < 0) nOrder = 0;
                    int nTmp = numberOfMeterGroups() - 1;
                    if (nOrder > nTmp) nOrder = nTmp;
                    bool done = false;

                    Dictionary<string, clsMeterItem> items = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsItemGroup ig = kvp.Value as clsItemGroup;
                        if (ig != null)
                        {
                            if (ig.MeterType == mt && (order == -1 || ig.Order == order) && !done)
                            {
                                ig.Order = nOrder;
                                done = true;
                            }
                            else if (ig.Order == nOrder)
                            {
                                if (bUp)
                                    ig.Order++;
                                else
                                    ig.Order--;
                            }
                        }
                    }
                }
                if (bRebuild) Rebuild();
            }
            public bool Enabled
            {
                get { return _enabled; }
                set { _enabled = value; }
            }
            public bool ShowOnRX
            {
                get { return _show_on_rx; }
                set { _show_on_rx = value; }
            }
            public bool ShowOnTX
            {
                get { return _show_on_tx; }
                set { _show_on_tx = value; }
            }
            private clsMeterItem itemFromID(string sId)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return null;
                    if (!_meterItems.ContainsKey(sId)) return null;

                    return _meterItems[sId];
                }
            }
            internal System.Drawing.RectangleF getBounds(string sId)
            {
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return System.Drawing.RectangleF.Empty;
                    Dictionary<string, clsMeterItem> items = itemsFromID(sId, false, true);
                    if (items == null) return System.Drawing.RectangleF.Empty;

                    float x = float.MaxValue;
                    float y = float.MaxValue;
                    float brx = float.MinValue;
                    float bry = float.MinValue;

                    foreach (KeyValuePair<string, clsMeterItem> kvp in items)
                    {
                        clsMeterItem mi = kvp.Value;

                        if(mi.TopLeft.X < x) x = mi.TopLeft.X;
                        if(mi.TopLeft.Y < y) y = mi.TopLeft.Y;
                        if (mi.TopLeft.X + mi.Size.Width > brx) brx = mi.TopLeft.X + mi.Size.Width;
                        if (mi.TopLeft.Y + mi.Size.Height > bry) bry = mi.TopLeft.Y + mi.Size.Height;
                    }

                    return new System.Drawing.RectangleF(x, y, brx - x, bry - y);
                }
            }
            internal Dictionary<string, clsMeterItem> itemsFromID(string sId, bool bIncludeTheParent = true, bool bOnlyChildren = false)
            {
                // obtains all items that have given ID and also the parent
                // note: the id must exist as a non parent unless the onlychildren flag is used
                lock (_meterItemsLock)
                {
                    if (_meterItems == null) return null;
                    if (!bOnlyChildren && !_meterItems.ContainsKey(sId)) return null;

                    Dictionary<string, clsMeterItem> lst = new Dictionary<string, clsMeterItem>();

                    foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems.Where(o => (o.Value.ParentID == sId) || (!bOnlyChildren && bIncludeTheParent && o.Value.ID == sId)))
                        lst.Add(kvp.Key, kvp.Value);

                    return lst;
                }
            }
            private bool hasReading(Reading reading)
            {
                bool bRet = false;
                foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems)
                {
                    if(kvp.Value.ReadingSource == reading)
                    {
                        bRet = true;
                        break;
                    }
                }
                return bRet;
            }
            public float GetBottom()
            {
                lock (_meterItemsLock)
                {
                    float fBottom = 0;

                    foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems)
                    {
                        float fTmp = kvp.Value.TopLeft.Y + kvp.Value.Size.Height;
                        if (fTmp > fBottom) fBottom = fTmp;
                    }

                    return fBottom;
                }
            }
            //public clsItemGroup GetItemGroup(string id, bool checkParent = false)
            //{
            //    lock (_meterItemsLock)
            //    {
            //        Dictionary<string, clsMeterItem> meterItems;
            //        if (checkParent)
            //        {
            //            meterItems = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP && (o.Value.ID == id || o.Value.ParentID == id)).ToDictionary(x => x.Key, x => x.Value);
            //        }
            //        else
            //        {
            //            meterItems = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP && o.Value.ID == id).ToDictionary(x => x.Key, x => x.Value);
            //        }

            //        if (meterItems.Count == 1)
            //        {
            //            return (clsItemGroup)meterItems.First().Value;
            //        }
            //        return null;
            //    }
            //}
            public int QuickestRXUpdate
            {
                get { return _quickestRXUpdate; }
            }
            public int QuickestTXUpdate
            {
                get { return _quickestTXUpdate; }
            }
            internal Dictionary<string, clsItemGroup> getMeterGroups()
            {
                lock (_meterItemsLock)
                {
                    Dictionary<string, clsMeterItem> meterItems = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    Dictionary<string, clsItemGroup> groupItems = new Dictionary<string, clsItemGroup>();

                    foreach (KeyValuePair<string, clsMeterItem> kvp in meterItems)
                    {
                        groupItems.Add(kvp.Value.ID, (clsItemGroup)kvp.Value);
                    }
                    return groupItems;
                }                
            }
            public void UpdateIntervals()
            {
                _quickestRXUpdate = QuickestUpdateInterval(false);
                _quickestTXUpdate = QuickestUpdateInterval(true);
            }
            public void Rebuild()
            {
                UpdateIntervals();

                setupSortedZOrder();

                lock (_meterItemsLock)
                {
                    // shift top
                    //Dictionary<string, clsMeterItem> meterItems = _meterItems.Where(o => o.Value.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP).ToDictionary(x => x.Key, x => x.Value);
                    Dictionary<string, clsItemGroup> groupItems = getMeterGroups();

                    float fTop = 0;

                    foreach (KeyValuePair<string, clsItemGroup> kvp in groupItems.OrderBy(o => o.Value.Order))
                    {
                        clsItemGroup ig = kvp.Value;

                        if (ig != null)
                        {
                            ig.TopLeft = new PointF(ig.TopLeft.X, fTop);

                            Dictionary<string, clsMeterItem> itemsInGroup = itemsFromID(ig.ID, false);

                            foreach (KeyValuePair<string, clsMeterItem> kvp2 in itemsInGroup)
                            {
                                clsMeterItem mi = kvp2.Value;
                                mi.DisplayTopLeft = new PointF(mi.TopLeft.X, mi.TopLeft.Y + fTop);
                            }

                            fTop += ig.Size.Height;
                        }
                    }
                }
            }
            private void setupSortedZOrder()
            {
                lock (_meterItemsLock)
                {
                    //_sortedMeterItemsForZOrder = _meterItems.OrderBy(o => o.Value.ZOrder).ToDictionary(x => x.Key, x => x.Value);
                    _sortedMeterItemsForZOrder = _meterItems.OrderBy(o => o.Value.ZOrder).Select(x => x.Value).ToList();
                }
            }
            internal void MouseUp(System.Windows.Forms.MouseEventArgs e, clsMeter m, clsClickBox cb)
            {
                if (cb.GotoGroup > 0)
                {
                    m.DisplayGroup = cb.GotoGroup;
                }
                else
                {
                    if (cb.ChangesGroup)
                    {
                        if (e.Button == MouseButtons.Left)
                            incrementDisplayGroup();
                        else if (e.Button == MouseButtons.Right)
                            decrementDisplayGroup();
                    }
                    if (cb.PerformsIncDec)
                    {
                        if (e.Button == MouseButtons.Left)
                        {
                            lock (_meterItemsLock)
                            {
                                incrementMeterItem(cb);
                            }
                        }
                        else if (e.Button == MouseButtons.Right)
                        {
                            lock (_meterItemsLock)
                            {
                                decrementMeterItem(cb);
                            }
                        }
                    }
                }
            }
            public bool MOX
            {
                get { return _mox; }
                set 
                {
                    bool changed = _mox != value;
                    _mox = value;
                    if (changed)
                    {
                        if (_mox)
                            _qso_start = DateTime.Now;
                        else
                            _qso_end = DateTime.Now;
                    }
                }
            }
            public long QsoDurationSeconds
            {
                get 
                { 
                    if (_mox) _qso_end = DateTime.Now;
                    return (long)(_qso_end - _qso_start).TotalSeconds; 
                }
            }
            public bool VFOSync
            {
                get { return _vfoA_sync; }
                set { _vfoA_sync = value; }
            }
            public bool VFOALock
            {
                get { return _vfoA_lock; }
                set { _vfoA_lock = value; }
            }
            public bool VFOBLock
            {
                get { return _vfoB_lock; }
                set { _vfoB_lock = value; }
            }
            public bool Split
            {
                get { return _split; }
                set { _split = value; }
            }
            public bool TXVFOb
            {
                get { return _txVfoB; }
                set { _txVfoB = value; }
            }
            public int TuneStepIndex
            {
                get { return _tune_step_index; }
                set
                {
                    _tune_step_index = value;
                }
            }
            public double VfoA
            {
                get { return _vfoA; }
                set 
                { 
                    _vfoA = value;
                    if(_rx == 1) _rx1VHForAbove = _vfoA >= _console.S9Frequency;
                }
            }
            public double VfoB
            {
                get { return _vfoB; }
                set 
                { 
                    _vfoB = value;
                    if (_rx == 2) _rx2VHForAbove = _vfoB >= _console.S9Frequency;
                }
            }
            public double VfoSub
            {
                get { return _vfoSub; }
                set { _vfoSub = value; }
            }
            public DSPMode ModeVfoA
            {
                get { return _modeVfoA; }
                set { _modeVfoA = value; }
            }
            public DSPMode ModeVfoB
            {
                get { return _modeVfoB; }
                set { _modeVfoB = value; }
            }
            public Band BandVfoA
            {
                get { return _bandVfoA; }
                set 
                { 
                    _bandVfoA = value;
                    updateBandText(true);
                }
            }
            public Band BandVfoB
            {
                get { return _bandVfoB; }
                set 
                { 
                    _bandVfoB = value;
                    updateBandText(false);
                }
            }
            public Band BandVfoASub
            {
                get { return _bandVfoASub; }
                set { _bandVfoASub = value; }
            }
            public Band TXBand
            {
                get { return _bandTX; }
                set { _bandTX = value; }
            }
            public Filter FilterVfoA
            {
                get { return _filterVfoA; }
                set { _filterVfoA = value; }
            }
            public Filter FilterVfoB
            {
                get { return _filterVfoB; }
                set { _filterVfoB = value; }
            }
            public string FilterVfoAName
            {
                get { return _filterVfoAname; }
                set { _filterVfoAname = value; }
            }
            public string FilterVfoBName
            {
                get { return _filterVfoBname; }
                set { _filterVfoBname = value; }
            }
            public bool RX2Enabled
            {
                get { return _rx2Enabled; }
                set { _rx2Enabled = value; }
            }
            public bool MultiRxEnabled
            {
                get { return _multiRxEnabled; }
                set { _multiRxEnabled = value; }
            }
            public bool TXEQEnabled
            {
                get { return _txeqEnabled; }
                set { _txeqEnabled = value; }
            }
            private void updateBandText(bool is_vfoA)
            {
                bool ok;
                if(_rx == 1)
                {
                    if (is_vfoA)
                        ok = DB.BandText(_vfoA, out _vfoA_band_text);
                    else
                        ok = DB.BandText(_vfoB, out _vfoB_band_text);
                }
                else
                {
                    if(!is_vfoA)
                        ok = DB.BandText(_vfoB, out _vfoB_band_text);
                }
            }
            public string VFOABandText
            {
                get { return _vfoA_band_text; }
            }
            public string VFOBBandText
            {
                get { return _vfoB_band_text; }
            }
            public bool LevelerEnabled
            {
                get { return _levelerEnabled; }
                set { _levelerEnabled = value; }
            }
            public bool CFCEnabled
            {
                get { return _cfcEnabled; }
                set { _cfcEnabled = value; }
            }
            public bool CompandEnabled
            {
                get { return _compandEnabled; }
                set { _compandEnabled = value; }
            }
            public bool QuickSplitEnabled
            {
                get { return _quickSplitEnabled; }
                set { _quickSplitEnabled = value; }
            }
            public BandGroups BandGroup
            {
                get { return _band_group; }
                set { _band_group = value; }
            }
            public float PadX { get { return _fPadX; } set { _fPadX = value; } }
            public float PadY { get { return _fPadY; } set { _fPadY = value; } }
            public float Height { get { return _fHeight; } set { _fHeight = value; } }

            public float XRatio { get { return _XRatio; } set { _XRatio = value; } }
            public float YRatio { get { return _YRatio; } set { _YRatio = value; } }
            public string Name { get { return _name; } set { _name = value; } }            
            public string ID
            { 
                get { return _sId; } 
                set { _sId = value; }
            }
            public int RX { get { return _rx; } set { _rx = value; } }
            public int QuickestUpdateInterval(bool mox)
            {
                lock (_meterItemsLock)
                {
                    int updateInterval = int.MaxValue;
                    foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems.Where(o => (o.Value.ItemType == clsMeterItem.MeterItemType.NEEDLE || 
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.H_BAR ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.MAGIC_EYE ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.VFO_DISPLAY ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.CLOCK ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.SIGNAL_TEXT_DISPLAY ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.SPACER ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.TEXT_OVERLAY ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.LED ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.WEB_IMAGE ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.BAND_BUTTONS ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.MODE_BUTTONS ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.FILTER_BUTTONS ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.ANTENNA_BUTTONS ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.ROTATOR ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.HISTORY ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.TUNESTEP_BUTTONS ||
                                                                o.Value.ItemType == clsMeterItem.MeterItemType.DISCORD_BUTTONS
                                                                ) && ( ((mox && o.Value.OnlyWhenTX) || (!mox && o.Value.OnlyWhenRX)) || (!o.Value.OnlyWhenTX && !o.Value.OnlyWhenRX) ) ))
                    {
                        clsMeterItem mi = kvp.Value;
                        if (mi.UpdateInterval < updateInterval) updateInterval = mi.UpdateInterval;
                    }
                    return updateInterval;
                }
            }
            private void addUpdateReading(ref Dictionary<Reading, object> all_readings, Reading reading, object value)
            {
                if (all_readings.ContainsKey(reading))
                    all_readings[reading] = value;
                else
                    all_readings.Add(reading, value);
            }
            public void Update(ref List<Reading> readingsUsed)
            {
                lock (_meterItemsLock)
                {
                    //build dictionary of all readings
                    Dictionary<Reading, object> all_readings = new Dictionary<Reading, object>();
                    List<clsMeterItem> data_nodes = new List<clsMeterItem>();

                    // this is called for each meter from the meter manager thread
                    foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems)
                    {
                        // now we need to update each item in the meter
                        clsMeterItem mi = kvp.Value;
                        if (mi.RequiresUpdate)
                        {
                            if (mi.ItemType != clsMeterItem.MeterItemType.DATA_OUT)
                            {
                                mi.Update(_rx, ref readingsUsed);
                            }
                            else
                            {
                                clsDataOut dat = mi as clsDataOut;
                                if (dat != null)
                                {
                                    if(dat.MMIOGuid != Guid.Empty) data_nodes.Add(mi);
                                }
                            }
                        }
                    }

                    if (data_nodes.Count > 0)
                    {
                        //copy all readings
                        foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems)
                        {
                            clsMeterItem mi = kvp.Value;
                            if (mi.ReadingSource != Reading.NONE && (mi.ItemType == clsMeterItem.MeterItemType.H_BAR ||
                                mi.ItemType == clsMeterItem.MeterItemType.NEEDLE || mi.ItemType == clsMeterItem.MeterItemType.MAGIC_EYE))
                            {
                                addUpdateReading(ref all_readings, mi.ReadingSource, mi.Value);
                            }
                            else if (mi.ReadingSource == Reading.NONE && mi.ItemType == clsMeterItem.MeterItemType.CLOCK)
                            {
                                DateTime now = DateTime.Now;
                                DateTime UTCnow = DateTime.UtcNow;

                                clsClock clk = mi as clsClock;
                                if (clk == null) continue; //skip

                                string sUtc;
                                string sUtcAmPm = "";
                                if (clk.Show24HourCLock)
                                    sUtc = UTCnow.ToString("HH:mm:ss");
                                else
                                {
                                    sUtc = UTCnow.ToString("h:mm:ss");
                                    sUtcAmPm = UTCnow.Hour >= 12 ? "pm" : "am";
                                }

                                string sLoc;
                                string sLocAmPm = "";
                                if (clk.Show24HourCLock)
                                    sLoc = now.ToString("HH:mm:ss");
                                else
                                {
                                    sLoc = now.ToString("h:mm:ss");
                                    sLocAmPm = now.Hour >= 12 ? "pm" : "am";
                                }

                                string sUtcDate = UTCnow.ToString("ddd d MMM yyyy");
                                string sLocDate = now.ToString("ddd d MMM yyyy");

                                addUpdateReading(ref all_readings, Reading.TIME_UTC, sUtc + sUtcAmPm);
                                addUpdateReading(ref all_readings, Reading.DATE_UTC, sUtcDate);
                                addUpdateReading(ref all_readings, Reading.TIME_LOC, sLoc + sLocAmPm);
                                addUpdateReading(ref all_readings, Reading.DATE_LOC, sLocDate);
                            }
                            else if (mi.ReadingSource == Reading.NONE && mi.ItemType == clsMeterItem.MeterItemType.VFO_DISPLAY)
                            {
                                addUpdateReading(ref all_readings, Reading.VFOA_FREQ, VfoA.ToString("f6"));
                                addUpdateReading(ref all_readings, Reading.VFOB_FREQ, VfoB.ToString("f6"));
                                bool vfoSub = RX == 1 && VfoSub >= 0 && RX2Enabled && (MultiRxEnabled || Split);
                                if (vfoSub) addUpdateReading(ref all_readings, Reading.VFOSUBA_FREQ, VfoSub.ToString("f6"));
                                addUpdateReading(ref all_readings, Reading.TX_FREQ, TXVFOb ? VfoB.ToString("f6") : VfoA.ToString("f6"));
                                addUpdateReading(ref all_readings, Reading.VFOA_BAND, clearB(BandVfoA.ToString().ToLower()));
                                addUpdateReading(ref all_readings, Reading.VFOB_BAND, clearB(BandVfoB.ToString().ToLower()));
                                if (vfoSub) addUpdateReading(ref all_readings, Reading.VFOSUBA_BAND, clearB(BandVfoASub.ToString().ToLower()));
                                addUpdateReading(ref all_readings, Reading.VFOA_FILTER_NAME, FilterVfoAName.ToLower());
                                addUpdateReading(ref all_readings, Reading.VFOB_FILTER_NAME, FilterVfoBName.ToLower());
                                addUpdateReading(ref all_readings, Reading.TX_BAND, clearB(TXVFOb ? BandVfoB.ToString().ToLower() : BandVfoA.ToString().ToLower()));
                                addUpdateReading(ref all_readings, Reading.VFOA_MODE, ModeVfoA.ToString().ToLower());
                                addUpdateReading(ref all_readings, Reading.VFOB_MODE, ModeVfoB.ToString().ToLower());
                                addUpdateReading(ref all_readings, Reading.SPLIT, Split.ToString().ToLower());
                                addUpdateReading(ref all_readings, Reading.RX2_ENABLED, RX2Enabled.ToString().ToLower());
                                addUpdateReading(ref all_readings, Reading.VFOB_TX, TXVFOb.ToString().ToLower());
                                if (RX == 1) addUpdateReading(ref all_readings, Reading.SUB_RX, MultiRxEnabled.ToString().ToLower());
                            }
                        }

                        // now just the datanodes
                        if (_rx == 1 || (_rx == 2 && _rx2Enabled))
                        {
                            foreach (clsMeterItem midatanode in data_nodes)
                            {
                                midatanode.Update(_rx, ref readingsUsed, all_readings);
                            }
                        }
                    }
                }
            }
            private string clearB(string b)
            {
                if (b.Left(1) == "b" || b.Left(1) == "B") return b.Substring(1);
                return b;
            }
            public int DelayForUpdate()
            {
                lock (_meterItemsLock)
                {
                    // this is called for each meter from the meter manager thread
                    int nDelay = int.MaxValue;
                    foreach (KeyValuePair<string, clsMeterItem> kvp in _meterItems)
                    {
                        clsMeterItem mi = kvp.Value;
                        int nTmp = mi.DelayUntilNextUpdate;
                        if (nTmp < nDelay) nDelay = nTmp;
                    }
                    if (nDelay == int.MaxValue) nDelay = 250;
                    return nDelay;
                }
            }
            //public Dictionary<string, clsMeterItem> SortedMeterItemsForZOrder
            public List<clsMeterItem> SortedMeterItemsForZOrder
            {
                get {
                    lock (_meterItemsLock)
                    {
                        return _sortedMeterItemsForZOrder;
                    }
                }
            }
            private void incrementDisplayGroup()
            {
                _displayGroup++;
                if (_displayGroup > _displayGroups.Count - 1) _displayGroup = 0;
            }
            private void decrementDisplayGroup()
            {
                _displayGroup--;
                if (_displayGroup < 0) _displayGroup = _displayGroups.Count - 1;
            }
            private void incrementMeterItem(clsClickBox cb)
            {
                if (cb == null || cb.RelatedMeterItem == null) return;

                cb.RelatedMeterItem.HandleIncrement();
            }
            private void decrementMeterItem(clsClickBox cb)
            {
                if (cb == null || cb.RelatedMeterItem == null) return;

                cb.RelatedMeterItem.HandleDecrement();
            }
            public int DisplayGroups
            {
                get { return _displayGroups.Count; }
            }
            public int DisplayGroup
            {
                get { return _displayGroup; }
                set
                {
                    _displayGroup = value;
                    if (_displayGroup < 0) _displayGroup = 0;
                    if (_displayGroup > _displayGroups.Count - 1) _displayGroup = _displayGroups.Count - 1;
                }
            }
            public void AddDisplayGroup(string sName)
            {
                if (_displayGroups.Contains(sName)) return;

                _displayGroups.Add(sName);
                _displayGroup = 0; // 0 is all
            }
            public void RemoveDisplayGroup(string sName)
            {
                if (!_displayGroups.Contains(sName)) return;

                _displayGroups.Remove(sName);
                _displayGroup = 0; // 0 is all
            }
            public string DisplayGroupText
            {
                get
                {
                    if (_displayGroup < 0) return "";
                    if (_displayGroup > _displayGroups.Count - 1) return "";

                    return _displayGroups[_displayGroup];
                }
            }
            public override string ToString()
            {
                string sRet;

                sRet = _sId + "|" +
                    _name + "|" +
                    _rx.ToString() + "|" +
                    _XRatio.ToString("f4") + "|" +
                    _YRatio.ToString("f4") + "|" +
                    _displayGroup.ToString() + "|" +
                    _fPadX.ToString("f4") + "|" +
                    _fPadY.ToString("f4") + "|" +
                    _fHeight.ToString("f4");

                return sRet;
            }
        }
        #endregion
        #region clsReading
        private class clsReading
        {
            public Reading Type;
            public float Reading;
            public bool Updated;
        }
        private class clsReadings
        {
            private Dictionary<Reading, clsReading> _latestReading;
            public clsReadings()
            {
                _latestReading = new Dictionary<Reading, clsReading>();

                for (int n = 0; n < (int)Reading.LAST; n++)
                {
                    clsReading cr = new clsReading() { Type = (Reading)n, Reading = -200f, Updated = false };
                    _latestReading.Add((Reading)n, cr);
                }
            }
            public float GetReading(Reading rt, bool useReading = false)
            {
                if (rt == Reading.NONE) return -200f;
                if (useReading) UseReading(rt);
                return _latestReading[rt].Reading;
            }
            public void SetReading(Reading rt, float value)
            {
                if (rt == Reading.NONE) return;
                _latestReading[rt].Reading = value;
                _latestReading[rt].Updated = true;
            }
            public bool RequiresUpdate(Reading rt)
            {
                if (rt == Reading.NONE) return false;
                return !_latestReading[rt].Updated;
            }
            public void UseReading(Reading rt)
            {
                if (rt == Reading.NONE) return;
                _latestReading[rt].Updated = false;
            }
        }
        #endregion
        
        #region DirectX
        //based on display.cs
        private class DXRenderer
        {
            private const SharpDX.Direct2D1.AlphaMode _ALPHA_MODE = SharpDX.Direct2D1.AlphaMode.Premultiplied;
            private bool _bDXSetup;
            private bool _dxDisplayThreadRunning;
            private Thread _dxRenderThread;
            private PresentFlags _NoVSYNCpresentFlag;
            private SharpDX.DXGI.Factory1 _factory1;
            private SharpDX.Direct3D11.Device _device;
            private int _nBufferCount;
            private Surface _surface;
            private SwapChain _swapChain;
            private SwapChain1 _swapChain1;
            private RenderTarget _renderTarget;
            private SharpDX.Direct2D1.Factory _factory;
            private bool _bAntiAlias;
            private Vector2 _pixelShift;
            private int _nVBlanks;
            private PictureBox _displayTarget;
            private int _oldRedrawDelay;
            private object _DXlock = new object();
            //
            private Dictionary<System.Drawing.Color, SharpDX.Direct2D1.Brush> _DXBrushes;
            private Color4 _backColour;
            private System.Drawing.Color _backgroundColour;
            private Dictionary<string, SharpDX.Direct2D1.Bitmap> _images;
            private Dictionary<string, BitmapBrush> _bitmap_brushes;
            //
            private Dictionary<string, SharpDX.DirectWrite.TextFormat> _textFormats; // fonts
            private SharpDX.DirectWrite.Factory _fontFactory;
            private Dictionary<string, SizeF> _stringMeasure;
            private Queue<string> _stringMeasureKeys;
            //

            private int _rx;
            private Console _console;

            private HiPerfTimer _objFrameStartTimer = new HiPerfTimer();
            private double _dElapsedFrameStart;
            private double _delta_time_ms;

            //fps          
            //private int _nFps = 0;
            //private int _nFrameCount = 0;            
            //private double _fLastTime;
            //private double _dElapsedFrameStart;
            //

            private string _sId;
            private clsMeter _meter;
            private bool _highlightEdge;
            private int _targetWidth;
            private int _targetHeight;
            private int _newTargetWidth;
            private int _newTargetHeight;
            private bool _targetVisible;
            private bool _enabled;
            private ColorInterpolator _color_interp;

            public DXRenderer(string sId, int rx, PictureBox target, Console c, clsMeter meter)
            {
                if (c == null || target == null) return;

                _delta_time_ms = 0;
                _dElapsedFrameStart = 0;
                _objFrameStartTimer.Start();

                _sId = sId;
                _rx = rx;
                _console = c;
                _meter = meter;
                _highlightEdge = false;
                _enabled = meter.Enabled;

                //dx
                _DXBrushes = new Dictionary<System.Drawing.Color, SharpDX.Direct2D1.Brush>();
                _textFormats = new Dictionary<string, SharpDX.DirectWrite.TextFormat>();
                _stringMeasure = new Dictionary<string, SizeF>();
                _stringMeasureKeys = new Queue<string>();

                _dxDisplayThreadRunning = false;
                _bAntiAlias = true;
                _bDXSetup = false;
                _NoVSYNCpresentFlag = PresentFlags.None;
                _pixelShift = new Vector2(0.5f, 0.5f);
                //_pixelShift = new Vector2(0f, 0f);
                _nVBlanks = Display.VerticalBlanks;
                _oldRedrawDelay = -1;
                _displayTarget = target;
                _displayTarget.Tag = sId; // use the tag to hold sId, we can then use this in mouse event OnMouseUp

                // targetWidth properties limit min size which prevents errors in font size calcs as one example
                targetWidth = target.Width;
                targetHeight = target.Height;
                _newTargetWidth = targetWidth;
                _newTargetHeight = targetHeight;
                _targetVisible = target.Visible;
                //

                _backgroundColour = System.Drawing.Color.Black;
                _backColour = convertColour(_backgroundColour);
                
                _images = new Dictionary<string, SharpDX.Direct2D1.Bitmap>();
                _bitmap_brushes = new Dictionary<string, BitmapBrush>();
                
                //fps
                //_fLastTime = _objFrameStartTimer.ElapsedMsec;
                //_dElapsedFrameStart = _objFrameStartTimer.ElapsedMsec;

                _displayTarget.Resize += target_Resize;
                _displayTarget.MouseUp += OnMouseUp;
                _displayTarget.MouseDown += OnMouseDown;
                _displayTarget.MouseWheel += OnMouseWheel;
                _displayTarget.MouseMove += OnMouseMove;
                _displayTarget.VisibleChanged += target_VisibleChanged;
                _displayTarget.MouseLeave += OnMouseLeave;
                _displayTarget.MouseEnter += OnMouseEnter;
                //_displayTarget.Click += OnClick;
                _displayTarget.MouseClick += OnMouseClick;
            }
            public bool SetVsync
            {
                get { return _nVBlanks != 0; }
                set
                {
                    _nVBlanks = value ? 1 : 0;
                }
            }
            private double deltaTimeMS
            {
                get { return _delta_time_ms; }
            }
            public void RunDisplay()
            {
                dxInit();

                _dxDisplayThreadRunning = false;
                _dxRenderThread = new Thread(new ThreadStart(dxRender))
                {
                    Name = "Multimeter DX Render Thread",
                    Priority = ThreadPriority.Lowest,
                    IsBackground = false
                };
                _dxRenderThread.SetApartmentState(ApartmentState.STA); //[2.10.3]
                _dxRenderThread.Start();
            }
            public string ID
            {
                get { return _sId; }
            }
            public bool HighlightEdge
            {
                get { return _highlightEdge; }
                set { _highlightEdge = value; }
            }
            public bool Enabled
            {
                get { return _enabled; }
                set { _enabled = value; }
            }
            public System.Drawing.Color BackgroundColour
            {
                get { return _backgroundColour; }
                set 
                {
                    _backgroundColour = value;
                    _backColour = convertColour(_backgroundColour);
                }
            }
            internal void RemoveAnySkinImages()
            {
                lock (_DXlock)
                {
                    List<string> keysWithBoolTag = _images
                                .Where(pair => pair.Value.Tag is bool)  // note: we need is bool because we also store GUID in here for webimage
                                .Select(pair => pair.Key)
                                .ToList();

                    foreach (string sKey in keysWithBoolTag)
                    {
                        RemoveDXImage(sKey);
                    }
                }
            }
            private void convertImageToDX(string sID, bool make_bitmap_brush = false)
            {
                if (_images.ContainsKey(sID)) return;

                System.Drawing.Bitmap cachedBMP = MeterManager.GetBitmap(sID);
                if (cachedBMP != null)
                {
                    SharpDX.Direct2D1.Bitmap dxImg = bitmapFromSystemBitmap(_renderTarget, cachedBMP, sID);
                    if (dxImg != null)
                    {
                        dxImg.Tag = cachedBMP.Tag;
                        _images.Add(sID, dxImg);

                        if (make_bitmap_brush && !_bitmap_brushes.ContainsKey(sID))
                        {
                            // also add the bitmap brush
                            BitmapBrush bb = new BitmapBrush(_renderTarget, dxImg, new BitmapBrushProperties()
                            {
                                ExtendModeX = ExtendMode.Clamp,
                                ExtendModeY = ExtendMode.Clamp,
                                InterpolationMode = BitmapInterpolationMode.Linear
                            });
                            _bitmap_brushes.Add(sID, bb);
                        }
                    }
                }
            }
            private int getMaxSamples()
            {
                //D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT 32u32
                //NOTE: can not be used with FlipDiscard atm unfortunately
                for (int n = 32; n>0; n /= 2)
                {
                    if(_device.CheckMultisampleQualityLevels(Format.B8G8R8A8_UNorm, n) > 0)
                    {
                        return n;
                    }
                }
                return 1;
            }
            private void dxInit(DriverType driverType = DriverType.Hardware)
            {
                // code based on display.cs
                if (_bDXSetup) return;

                try
                {
                    DeviceCreationFlags debug = DeviceCreationFlags.None;

                    // to get this to work, need to target the os
                    // https://www.prugg.at/2019/09/09/properly-detect-windows-version-in-c-net-even-windows-10/
                    // you need to enable operating system support in the app1.manifest file, otherwise majVers will not report 10+
                    // note: windows 10, 11, server 2016, server 2019, server 2022 all use the windows 10 os id in the manifest file at this current time
                    int majVers = Environment.OSVersion.Version.Major;
                    int minVers = Environment.OSVersion.Version.Minor;

                    SharpDX.Direct3D.FeatureLevel[] featureLevels;

                    if (majVers >= 10) // win10 + 11
                    {
                        featureLevels = new SharpDX.Direct3D.FeatureLevel[] {
                            SharpDX.Direct3D.FeatureLevel.Level_12_1,
                            SharpDX.Direct3D.FeatureLevel.Level_12_0,
                            SharpDX.Direct3D.FeatureLevel.Level_11_1, // windows 8 and up
                            SharpDX.Direct3D.FeatureLevel.Level_11_0, // windows 7 and up (level 11 was only partial on 7, not 11_1)
                            SharpDX.Direct3D.FeatureLevel.Level_10_1,
                            SharpDX.Direct3D.FeatureLevel.Level_10_0,
                            SharpDX.Direct3D.FeatureLevel.Level_9_3,
                            SharpDX.Direct3D.FeatureLevel.Level_9_2,
                            SharpDX.Direct3D.FeatureLevel.Level_9_1
                        };
                        _NoVSYNCpresentFlag = PresentFlags.DoNotWait;
                    }
                    else if (majVers == 6 && minVers >= 2) // windows 8, windows 8.1
                    {
                        featureLevels = new SharpDX.Direct3D.FeatureLevel[] {
                            SharpDX.Direct3D.FeatureLevel.Level_11_1, // windows 8 and up
                            SharpDX.Direct3D.FeatureLevel.Level_11_0, // windows 7 and up (level 11 was only partial on 7, not 11_1)
                            SharpDX.Direct3D.FeatureLevel.Level_10_1,
                            SharpDX.Direct3D.FeatureLevel.Level_10_0,
                            SharpDX.Direct3D.FeatureLevel.Level_9_3,
                            SharpDX.Direct3D.FeatureLevel.Level_9_2,
                            SharpDX.Direct3D.FeatureLevel.Level_9_1
                        };
                        _NoVSYNCpresentFlag = PresentFlags.DoNotWait;
                    }
                    else if (majVers == 6 && minVers < 2) // windows 7, 2008 R2, 2008, vista
                    {
                        featureLevels = new SharpDX.Direct3D.FeatureLevel[] {
                            SharpDX.Direct3D.FeatureLevel.Level_11_0, // windows 7 and up (level 11 was only partial on 7, not 11_1)
                            SharpDX.Direct3D.FeatureLevel.Level_10_1,
                            SharpDX.Direct3D.FeatureLevel.Level_10_0,
                            SharpDX.Direct3D.FeatureLevel.Level_9_3,
                            SharpDX.Direct3D.FeatureLevel.Level_9_2,
                            SharpDX.Direct3D.FeatureLevel.Level_9_1
                        };
                        _NoVSYNCpresentFlag = PresentFlags.None;
                    }
                    else
                    {
                        featureLevels = new SharpDX.Direct3D.FeatureLevel[] {
                            SharpDX.Direct3D.FeatureLevel.Level_9_1
                        };
                        _NoVSYNCpresentFlag = PresentFlags.None;
                    }

                    _factory1 = new SharpDX.DXGI.Factory1();
                    
                    _device = new SharpDX.Direct3D11.Device(driverType, debug | DeviceCreationFlags.PreventAlteringLayerSettingsFromRegistry | DeviceCreationFlags.BgraSupport/* | DeviceCreationFlags.SingleThreaded*/, featureLevels);

                    SharpDX.DXGI.Device1 device1 = _device.QueryInterfaceOrNull<SharpDX.DXGI.Device1>();
                    if (device1 != null)
                    {
                        device1.MaximumFrameLatency = 1;
                        Utilities.Dispose(ref device1);
                        device1 = null;
                    }

                    //this code should ideally be used to prevent use of flip if vsync is 0
                    //but is not used at this time
                    //SharpDX.DXGI.Factory5 f5 = factory.QueryInterfaceOrNull<SharpDX.DXGI.Factory5>();
                    //bool bAllowTearing = false;
                    //if(f5 != null)
                    //{
                    //    int size = Marshal.SizeOf(typeof(bool));
                    //    IntPtr pBool = Marshal.AllocHGlobal(size);

                    //    f5.CheckFeatureSupport(SharpDX.DXGI.Feature.PresentAllowTearing, pBool, size);

                    //    bAllowTearing = Marshal.ReadInt32(pBool) == 1;

                    //    Marshal.FreeHGlobal(pBool);
                    //}
                    //

                    // check if the device has a factory4 interface
                    // if not, then we need to use old bitplit swapeffect
                    SwapEffect swapEffect;

                    SharpDX.DXGI.Factory4 factory4 = _factory1.QueryInterfaceOrNull<SharpDX.DXGI.Factory4>();
                    bool bFlipPresent = false;
                    if (factory4 != null)
                    {
                        /*if (!_bUseLegacyBuffers)*/ bFlipPresent = true;
                        Utilities.Dispose(ref factory4);
                        factory4 = null;
                    }

                    //https://walbourn.github.io/care-and-feeding-of-modern-swapchains/
                    swapEffect = bFlipPresent ? SwapEffect.FlipDiscard : SwapEffect.Discard; //NOTE: FlipSequential should work, but is mostly used for storeapps
                    _nBufferCount = bFlipPresent ? 2 : 1;

                    //int multiSample = 2; // eg 2 = MSAA_2, 2 times multisampling
                    //int maxQuality = _device.CheckMultisampleQualityLevels(Format.B8G8R8A8_UNorm, multiSample) - 1;
                    //maxQuality = Math.Max(0, maxQuality);

                    //20 fps is the fastest that any meter can be defined as 50ms is update limit
                    _oldRedrawDelay = 20;
                    ModeDescription md = new ModeDescription(targetWidth, targetHeight,
                                                               new Rational(_oldRedrawDelay, 1)/*console.DisplayFPS, 1)*/, Format.B8G8R8A8_UNorm);

                    md.ScanlineOrdering = DisplayModeScanlineOrder.Progressive;
                    md.Scaling = DisplayModeScaling.Centered;
                    
                    SwapChainDescription desc = new SwapChainDescription()
                    {
                        BufferCount = _nBufferCount,
                        ModeDescription = md,
                        IsWindowed = true,
                        OutputHandle = _displayTarget.Handle,
                        //SampleDescription = new SampleDescription(multiSample, maxQuality),
                        SampleDescription = new SampleDescription(1, 0), // no multi sampling (1 sample), no antialiasing
                        SwapEffect = swapEffect,
                        Usage = Usage.RenderTargetOutput,// | Usage.BackBuffer,  // dont need usage.backbuffer as it is implied
                        Flags = SwapChainFlags.None,
                    };

                    _factory1.MakeWindowAssociation(_displayTarget.Handle, WindowAssociationFlags.IgnoreAll);
                    _swapChain = new SwapChain(_factory1, _device, desc);
                    _swapChain1 = _swapChain.QueryInterface<SwapChain1>();

                    _factory = new SharpDX.Direct2D1.Factory(FactoryType.SingleThreaded, DebugLevel.None);
                    
                    _surface = _swapChain1.GetBackBuffer<Surface>(0);

                    RenderTargetProperties rtp = new RenderTargetProperties(new SharpDX.Direct2D1.PixelFormat(_swapChain.Description.ModeDescription.Format, _ALPHA_MODE));
                    _renderTarget = new RenderTarget(_factory, _surface, rtp);

                    if (debug == DeviceCreationFlags.Debug)
                    {
                        _device.DebugName = "MeterDeviceDB_" + _rx.ToString();
                        _swapChain.DebugName = "MeterSwapChainDB_" + _rx.ToString();
                        _swapChain1.DebugName = "MeterSwapChain1DB_" + _rx.ToString();
                        _surface.DebugName = "MeterSurfaceDB_" + _rx.ToString();
                    }
                    else
                    {
                        _device.DebugName = ""; // used in shutdown
                    }

                    _bDXSetup = true;

                    setupAliasing();

                    buildDXFonts();
                }
                catch (Exception e)
                {
                    // issue setting up dx
                    ShutdownDX();
                    MessageBox.Show("Problem initialising Meter DirectX !" + System.Environment.NewLine + System.Environment.NewLine + "[" + e.ToString() + "]", "DirectX", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, Common.MB_TOPMOST);
                }
            }
            private void resetDX2DModeDescription(int fps)
            {
                // used to reset the FPS on the swapChain
                try
                {
                    lock (_DXlock)
                    {
                        if (!_bDXSetup) return;

                        ModeDescription modeDesc = new ModeDescription(targetWidth, targetHeight,
                                                           new Rational(fps, 1), Format.B8G8R8A8_UNorm);
                        _swapChain1.ResizeTarget(ref modeDesc);

                        //dont need to resize here, as targetwidth and targetheight will not be different
                        //to the rendertarget

                        //Debug.Print(">> dx is resizing from resetDX2DModeDescription <<");
                        //resizeDX();
                    }
                }
                catch (Exception e)
                {

                }
            }
            public void ShutdownDX(bool bFromRenderThread = false)
            {
                if (_displayTarget != null)
                {
                    _displayTarget.Resize -= target_Resize;
                    _displayTarget.MouseUp -= OnMouseUp;
                    _displayTarget.MouseDown -= OnMouseDown;
                    _displayTarget.MouseWheel -= OnMouseWheel;
                    _displayTarget.MouseMove -= OnMouseMove;
                    _displayTarget.MouseLeave -= OnMouseLeave;
                    _displayTarget.MouseEnter -= OnMouseEnter;
                    _displayTarget.VisibleChanged -= target_VisibleChanged;
                    //_displayTarget.Click -= OnClick;
                    _displayTarget.MouseClick -= OnMouseClick;
                }

                if (!_bDXSetup) return;

                _dxDisplayThreadRunning = false;
                if (_dxRenderThread != null && _dxRenderThread.IsAlive && !bFromRenderThread)
                {
                    int nWait = 0;
                    clsMeter m = _meter;
                    lock (m._meterItemsLock)
                    {
                        int nTmp = m.MOX ? m.QuickestTXUpdate : m.QuickestRXUpdate;
                        if (nTmp > nWait) nWait = nTmp;
                    }
                    if (nWait > 250) nWait = 250; // maxint can be returned if no meteritem entries

                    _dxRenderThread.Join(nWait + 100); // slightly longer
                }

                try
                {
                    if (_device != null && _device.ImmediateContext != null)
                    {
                        _device.ImmediateContext.ClearState();
                        _device.ImmediateContext.Flush();
                    }

                    RemoveAllDXImages();

                    releaseDXFonts();
                    releaseDXResources();

                    Utilities.Dispose(ref _renderTarget);
                    Utilities.Dispose(ref _swapChain1);
                    Utilities.Dispose(ref _swapChain);
                    Utilities.Dispose(ref _surface);
                    Utilities.Dispose(ref _factory);
                    Utilities.Dispose(ref _factory1);

                    _renderTarget = null;
                    _swapChain1 = null;
                    _swapChain = null;
                    _surface = null;
                    _factory = null;
                    _factory1 = null;

                    if (_device != null && _device.ImmediateContext != null)
                    {
                        SharpDX.Direct3D11.DeviceContext dc = _device.ImmediateContext;
                        Utilities.Dispose(ref dc);
                        dc = null;
                    }

                    SharpDX.Direct3D11.DeviceDebug ddb = null;
                    if (_device != null && _device.DebugName != "")
                    {
                        ddb = new SharpDX.Direct3D11.DeviceDebug(_device);
                        ddb.ReportLiveDeviceObjects(ReportingLevel.Detail);
                    }

                    if (ddb != null)
                    {
                        Utilities.Dispose(ref ddb);
                        ddb = null;
                    }
                    Utilities.Dispose(ref _device);
                    _device = null;

                    _bDXSetup = false;
                }
                catch (Exception e)
                {
                    MessageBox.Show("Problem Shutting Down Meter DirectX !" + System.Environment.NewLine + System.Environment.NewLine + "[" + e.ToString() + "]", "DirectX", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, Common.MB_TOPMOST);
                }
            }
            internal void RemoveAllDXImages()
            {
                if (_images == null || _images.Count == 0) return;

                lock (_DXlock)
                {
                    foreach (KeyValuePair<string, SharpDX.Direct2D1.Bitmap> kvp in _images)
                    {
                        SharpDX.Direct2D1.Bitmap b = kvp.Value;
                        Utilities.Dispose(ref b);
                    }
                    foreach (KeyValuePair<string, BitmapBrush> kvp in _bitmap_brushes)
                    {
                        SharpDX.Direct2D1.BitmapBrush b = kvp.Value;
                        Utilities.Dispose(ref b);
                    }
                    _images.Clear();
                    _bitmap_brushes.Clear();
                }
            }
            internal void RemoveDXImage(string sKey)
            {
                if (_images == null || _images.Count == 0) return;

                lock (_DXlock)
                {
                    if (_images.ContainsKey(sKey))
                    {
                        SharpDX.Direct2D1.Bitmap b = _images[sKey];
                        Utilities.Dispose(ref b);
                        _images.Remove(sKey);
                    }
                    if (_bitmap_brushes.ContainsKey(sKey))
                    {
                        BitmapBrush b = _bitmap_brushes[sKey];
                        Utilities.Dispose(ref b);
                        _bitmap_brushes.Remove(sKey);
                    }
                }
            }
            //private void calcFps()
            //{
            //    _nFrameCount++;
            //    if (_dElapsedFrameStart >= _fLastTime + 1000)
            //    {
            //        double late = _dElapsedFrameStart - (_fLastTime + 1000);
            //        if (late > 2000 || late < 0) late = 0; // ignore if too late
            //        _nFps = _nFrameCount;
            //        _nFrameCount = 0;
            //        _fLastTime = _dElapsedFrameStart - late;
            //    }
            //}
            private void dxRender()
            {
                if (!_bDXSetup) return;

                HiPerfTimer objStopWatch = new HiPerfTimer();

                try
                {
                    _dxDisplayThreadRunning = true;

                    while (_dxDisplayThreadRunning)
                    {
                        int height = int.MinValue;
                        int nSleepTime = int.MaxValue;

                        objStopWatch.Reset();

                        if (_enabled && _targetVisible)
                        {
                            //fps
                            //_dElapsedFrameStart = _objFrameStartTimer.ElapsedMsec;

                            lock (_DXlock)
                            {
                                if (targetWidth != _newTargetWidth || targetHeight != _newTargetHeight)
                                {
                                    targetWidth = _newTargetWidth;
                                    targetHeight = _newTargetHeight;

                                    //Debug.Print(">> dx is resizing from dxRender <<");
                                    bool bOk = resizeDX();
                                    if (!bOk) break; // exit do while as resizeDx will have thrown an exception and called shutdowndx
                                }

                                _renderTarget.BeginDraw();

                                // middle pixel align shift
                                Matrix3x2 t = _renderTarget.Transform;
                                t.TranslationVector = _pixelShift;
                                _renderTarget.Transform = t;

                                // background for entire form/area?
                                _renderTarget.Clear(_backColour);

                                nSleepTime = drawMeters(out height);
                                if (nSleepTime > 250) nSleepTime = 250; // sleep max of 250ms for some sensible redraw
                                                                        // maxint can be returned if no meteritem entries

                                if (_highlightEdge)
                                {
                                    SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, targetWidth - 1f, targetHeight - 1f);
                                    rect.Inflate(-8, -8);
                                    _renderTarget.DrawRectangle(rect, getDXBrushForColour(System.Drawing.Color.FromArgb(192, System.Drawing.Color.DarkOrange)), 16f);
                                }

                                //calcFps();
                                //_renderTarget.DrawText(_nFps.ToString(), getDXTextFormatForFont("Trebuchet MS", 18, FontStyle.Regular), new SharpDX.RectangleF(10, 0, float.PositiveInfinity, float.PositiveInfinity), getDXBrushForColour(System.Drawing.Color.White), DrawTextOptions.None);

                                // undo the translate
                                _renderTarget.Transform = Matrix3x2.Identity;

                                _renderTarget.EndDraw();

                                // render
                                // note: the only way to have Present non block when using vsync number of blanks 0 , is to use DoNotWait
                                // however the gpu will error if it is busy doing something and the data can not be queued
                                // It will error and just ignore everything, we try present and ignore the 0x887A000A error
                                PresentFlags pf = _nVBlanks == 0 ? _NoVSYNCpresentFlag : PresentFlags.None;
                                Result r = _swapChain1.TryPresent(_nVBlanks, pf);

                                // check fps delay
                                if (nSleepTime != _oldRedrawDelay)
                                {
                                    if (_oldRedrawDelay != -1) resetDX2DModeDescription(1000 / nSleepTime);
                                    _oldRedrawDelay = nSleepTime;
                                }
                                //

                                if (r != Result.Ok && r != 0x887A000A)
                                {
                                    string sMsg = "";
                                    if (r == 0x887A0001) sMsg = "Present Device Invalid Call" + Environment.NewLine + "" + Environment.NewLine + "[ " + r.ToString() + " ]";    //DXGI_ERROR_INVALID_CALL
                                    if (r == 0x887A0007) sMsg = "Present Device Reset" + Environment.NewLine + "" + Environment.NewLine + "[ " + r.ToString() + " ]";           //DXGI_ERROR_DEVICE_RESET
                                    if (r == 0x887A0005) sMsg = "Present Device Removed" + Environment.NewLine + "" + Environment.NewLine + "[ " + r.ToString() + " ]";         //DXGI_ERROR_DEVICE_REMOVED
                                    if (r == 0x88760870) sMsg = "Present Device DD3DDI Removed" + Environment.NewLine + "" + Environment.NewLine + "[ " + r.ToString() + " ]";  //D3DDDIERR_DEVICEREMOVED
                                                                                                                                                                                //if (r == 0x087A0001) sMsg = "Present Device Occluded" + Environment.NewLine + "" + Environment.NewLine + "[ " + r.ToString() + " ]";      //DXGI_STATUS_OCCLUDED
                                                                                                                                                                                //(ignored in the preceding if statement) if (r == 0x887A000A) sMsg = "Present Device Still Drawping" + Environment.NewLine + "" + Environment.NewLine + "[ " + r.ToString() + " ]"; //DXGI_ERROR_WAS_STILL_DRAWING

                                    if (sMsg != "") throw (new Exception(sMsg));
                                }
                            }
                            
                            int nMs = (int)objStopWatch.ElapsedMsec; // dont worry about fractions of ms
                            nSleepTime -= nMs;
                            if (nSleepTime < 1) nSleepTime = 1;
                            Thread.Sleep(nSleepTime);

                            _delta_time_ms = objStopWatch.ElapsedMsec;
                        }
                        else
                        {
                            Thread.Sleep(250); // if not visible, sleep for quarter second
                            _delta_time_ms = objStopWatch.ElapsedMsec;
                        }

                        if (height != int.MinValue)
                            MeterManager.SetContainerHeight(_meter.ID, height);
                    }
                }
                catch (Exception e)
                {
                    ShutdownDX(true);
                    MessageBox.Show("Problem in DirectX Meter Renderer !" + System.Environment.NewLine + System.Environment.NewLine + "[ " + e.ToString() + " ]", "DirectX", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, Common.MB_TOPMOST);
                }
            }
            private void setupAliasing()
            {
                if (!_bDXSetup) return;

                if (_bAntiAlias)
                    _renderTarget.AntialiasMode = AntialiasMode.PerPrimitive; // this will antialias even if multisampling is off
                else
                    _renderTarget.AntialiasMode = AntialiasMode.Aliased; // this will result in non antialiased lines only if multisampling = 1

                _renderTarget.TextAntialiasMode = TextAntialiasMode.Default;
            }
            public int VerticalBlanks
            {
                get { return _nVBlanks; }
                set
                {
                    int v = value;
                    if (v < 0) v = 0;
                    if (v > 4) v = 4;
                    _nVBlanks = v;
                }
            }
            private int targetWidth
            {
                get
                {
                    //return _displayTarget.Width; 
                    return _targetWidth;
                }
                set
                {
                    int tmp = value;
                    if (tmp < ucMeter.MIN_CONTAINER_WIDTH) tmp = ucMeter.MIN_CONTAINER_WIDTH; //see resize drag in ucmeter control

                    _targetWidth = tmp;
                    
                }
            }
            private int targetHeight
            {
                get
                {
                    //return _displayTarget.Height; 
                    return _targetHeight;
                }
                set
                {
                    int tmp = value;
                    if (tmp < ucMeter.MIN_CONTAINER_HEIGHT) tmp = ucMeter.MIN_CONTAINER_HEIGHT; //see resize drag in ucmeter control

                    _targetHeight = tmp;
                }
            }
            private void releaseDXResources()
            {
                if (!_bDXSetup) return;

                clearAllDynamicBrushes();
                clearAllDynamicTextFormats();
            }
            private void buildDXFonts()
            {
                if (!_bDXSetup) return;

                _fontFactory = new SharpDX.DirectWrite.Factory();
            }
            private void releaseDXFonts()
            {
                if (!_bDXSetup) return;

                if (_fontFactory != null) Utilities.Dispose(ref _fontFactory);

                _fontFactory = null;
            }
            private void clearAllDynamicBrushes()
            {
                if (!_bDXSetup || _DXBrushes == null) return;

                foreach (KeyValuePair<System.Drawing.Color, SharpDX.Direct2D1.Brush> kvp in _DXBrushes)
                {
                    SharpDX.Direct2D1.Brush b = kvp.Value;
                    Utilities.Dispose(ref b);
                    b = null;
                }

                _DXBrushes.Clear();
                _DXBrushes = null;
            }
            private void clearAllDynamicTextFormats()
            {
                if (!_bDXSetup || _textFormats == null) return;

                foreach (KeyValuePair<string, SharpDX.DirectWrite.TextFormat> kvp in _textFormats)
                {
                    SharpDX.DirectWrite.TextFormat tf = kvp.Value;
                    Utilities.Dispose(ref tf);
                    tf = null;
                }

                _textFormats.Clear();
                _textFormats = null;

                if (_fontFactory != null) Utilities.Dispose(ref _fontFactory);
                _fontFactory = null;
            }
            private SharpDX.Direct2D1.Brush getDXBrushForColour(System.Drawing.Color c, int replaceAlpha = -1)
            {
                if (!_bDXSetup) return null;
                if (c == System.Drawing.Color.Transparent) replaceAlpha = 0;

                System.Drawing.Color newC;
                if (replaceAlpha >= 0 && replaceAlpha <= 255)
                    newC = System.Drawing.Color.FromArgb(replaceAlpha, c.R, c.G, c.B); // override the alpha
                else
                    newC = c;

                if (_DXBrushes.ContainsKey(newC)) return _DXBrushes[newC];

                SolidBrush sb = new SolidBrush(newC);
                SharpDX.Direct2D1.Brush b = convertBrush(sb);
                sb.Dispose();

                _DXBrushes.Add(newC, b);

                return b;
            }
            private SharpDX.Direct2D1.SolidColorBrush convertBrush(SolidBrush b)
            {
                return new SharpDX.Direct2D1.SolidColorBrush(_renderTarget, convertColour(b.Color));
            }
            private SharpDX.Color4 convertColour(System.Drawing.Color c)
            {
                return new SharpDX.Color4(c.R / 255f, c.G / 255f, c.B / 255f, c.A / 255f);
            }
            private SharpDX.DirectWrite.TextFormat getDXTextFormatForFont(string sFontFamily, float emSize, FontStyle style, bool bAlignRight = false)
            {
                if (!_bDXSetup) return null;

                string sKey = sFontFamily + emSize.ToString("0.0") + style.ToString();

                if (_textFormats.ContainsKey(sKey)) return _textFormats[sKey];

                try
                {
                    SharpDX.DirectWrite.FontWeight fontWeight = SharpDX.DirectWrite.FontWeight.Regular;
                    SharpDX.DirectWrite.FontStyle fontStyle = SharpDX.DirectWrite.FontStyle.Normal;
                    if (((int)style & (int)FontStyle.Bold) == (int)FontStyle.Bold) fontWeight = SharpDX.DirectWrite.FontWeight.Bold;
                    if (((int)style & (int)FontStyle.Italic) == (int)FontStyle.Italic) fontStyle = SharpDX.DirectWrite.FontStyle.Italic;

                    SharpDX.DirectWrite.TextFormat tf = new SharpDX.DirectWrite.TextFormat(_fontFactory, sFontFamily, fontWeight, fontStyle, (emSize / 72) * _renderTarget.DotsPerInch.Width);
                    tf.WordWrapping = SharpDX.DirectWrite.WordWrapping.NoWrap;
                    if(bAlignRight)
                        tf.TextAlignment = SharpDX.DirectWrite.TextAlignment.Trailing;

                    _textFormats.Add(sKey, tf);

                    return tf;                    
                }
                catch { }

                return null;
            }
            private bool resizeDX()
            {
                lock (_DXlock)
                {
                    if (!_bDXSetup) return false;

                    try
                    {
                        Utilities.Dispose(ref _renderTarget);
                        Utilities.Dispose(ref _surface);

                        _renderTarget = null;
                        _surface = null;
                        
                        _device.ImmediateContext.ClearState();
                        _device.ImmediateContext.Flush();

                        _swapChain1.ResizeBuffers(_nBufferCount, targetWidth, targetHeight, _swapChain.Description.ModeDescription.Format, SwapChainFlags.None);
                        _surface = _swapChain1.GetBackBuffer<Surface>(0);

                        RenderTargetProperties rtp = new RenderTargetProperties(new SharpDX.Direct2D1.PixelFormat(_swapChain.Description.ModeDescription.Format, _ALPHA_MODE));
                        _renderTarget = new RenderTarget(_factory, _surface, rtp);

                        setupAliasing();

                        // clear measure string cache
                        _stringMeasure.Clear();
                        _stringMeasureKeys.Clear();
                        //

                        return true;
                    }
                    catch (Exception e)
                    {
                        ShutdownDX();
                        MessageBox.Show("DirectX resizeDX() Meter failure\n\nThis can sometimes be caused by other programs 'hooking' into directX," +
                            "such as GFX card control software (eg, EVGA Precision Xoc). Close down Thetis, quit as many 'system tray' and other\n" +
                            "things as possible and try again.\n\n" + e.Message, "DirectX", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, Common.MB_TOPMOST);
                        return false;
                    }
                }
            }
            //
            private void target_Resize(object sender, System.EventArgs e)
            {
                //Debug.Print(">> target resizing <<");
                //Debug.Print($"wdith new {_newTargetWidth} was {_displayTarget.Width} , height new {_newTargetHeight} was {_displayTarget.Height}");
                _newTargetWidth = _displayTarget.Width;
                _newTargetHeight = _displayTarget.Height;
            }
            private void target_VisibleChanged(object sender, System.EventArgs e)
            {
                //Debug.Print(">> target visible changed <<");

                _targetVisible = _displayTarget.Visible;
            }
            private void OnMouseEnter(object sender, System.EventArgs e)
            {
                PictureBox pb = sender as PictureBox;
                if (pb == null) return;
                string sId = pb.Tag.ToString();
                if (!_meters.ContainsKey(sId)) return;

                clsMeter m = _meters[sId];

                lock (m._meterItemsLock)
                {
                    if (m.SortedMeterItemsForZOrder != null)
                    {
                        foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                        {
                            mi.MouseEntered = true;
                        }
                    }
                }
            }
            private void OnMouseLeave(object sender, System.EventArgs e)
            {
                PictureBox pb = sender as PictureBox;
                if (pb == null) return;
                string sId = pb.Tag.ToString();
                if (!_meters.ContainsKey(sId)) return;

                clsMeter m = _meters[sId];

                lock (m._meterItemsLock)
                {
                    if (m.SortedMeterItemsForZOrder != null)
                    {
                        foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                        {
                            mi.MouseEntered = false;
                            mi.MouseMovePoint = new PointF(float.MinValue, float.MinValue);
                        }
                    }
                }
            }
            private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
            {
                lock (_metersLock)
                {
                    PictureBox pb = sender as PictureBox;
                    if (pb == null) return;
                    string sId = pb.Tag.ToString();
                    if (!_meters.ContainsKey(sId)) return;

                    clsMeter m = _meters[sId];

                    lock (m._meterItemsLock)
                    {
                        if (m.SortedMeterItemsForZOrder != null)
                        {
                            float tw = targetWidth - 1f;// 0.5f;
                            float rw = m.XRatio;
                            float rh = m.YRatio;

                            SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                            foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                            {
                                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                float w = rect.Width * (mi.Size.Width / m.XRatio);
                                float h = rect.Height * (mi.Size.Height / m.YRatio);

                                SharpDX.RectangleF clickRect = new SharpDX.RectangleF(x, y, w, h);

                                if (clickRect.Contains(new SharpDX.Point(e.X, e.Y)))
                                {
                                    mi.MouseMovePoint = new PointF(e.X, e.Y);
                                    mi.MouseEntered = true;
                                }
                                else
                                {
                                    mi.MouseMovePoint = new PointF(e.X, e.Y);
                                    mi.MouseEntered = false;
                                }
                            }
                        }
                    }
                    HandledMouseEventArgs handledEventArgs = e as HandledMouseEventArgs;
                    if (handledEventArgs != null)
                    {
                        handledEventArgs.Handled = true;
                    }
                }
            }
            private void OnMouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
            {
                lock (_metersLock)
                {
                    PictureBox pb = sender as PictureBox;
                    if (pb == null) return;
                    string sId = pb.Tag.ToString();
                    if (!_meters.ContainsKey(sId)) return;

                    clsMeter m = _meters[sId];

                    lock (m._meterItemsLock)
                    {
                        if (m.SortedMeterItemsForZOrder != null)
                        {
                            float tw = targetWidth - 1f;
                            float rw = m.XRatio;
                            float rh = m.YRatio;

                            SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                            foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                            {
                                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                float w = rect.Width * (mi.Size.Width / m.XRatio);
                                float h = rect.Height * (mi.Size.Height / m.YRatio);

                                SharpDX.RectangleF clickRect = new SharpDX.RectangleF(x, y, w, h);

                                if (clickRect.Contains(new SharpDX.Point(e.X, e.Y)))
                                {
                                    mi.MouseEntered = true;
                                    int number_of_moves = e.Delta * SystemInformation.MouseWheelScrollLines / 120;
                                    mi.MouseWheel(number_of_moves);
                                }
                                else
                                {
                                    mi.MouseEntered = false;
                                }
                            }
                        }
                    }
                    HandledMouseEventArgs handledEventArgs = e as HandledMouseEventArgs;
                    if (handledEventArgs != null)
                    {
                        handledEventArgs.Handled = true;
                    }
                }
            }
            private void OnMouseClick(object sender, MouseEventArgs e)
            {
                lock (_metersLock)
                {
                    PictureBox pb = sender as PictureBox;
                    if (pb == null) return;
                    string sId = pb.Tag.ToString();
                    if (!_meters.ContainsKey(sId)) return;

                    clsMeter m = _meters[sId];

                    lock (m._meterItemsLock)
                    {
                        if (m.SortedMeterItemsForZOrder != null)
                        {
                            float tw = targetWidth - 1f;// 0.5f;
                            float rw = m.XRatio;
                            float rh = m.YRatio;

                            SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                            foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                            {
                                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                float w = rect.Width * (mi.Size.Width / m.XRatio);
                                float h = rect.Height * (mi.Size.Height / m.YRatio);

                                SharpDX.RectangleF clickRect = new SharpDX.RectangleF(x, y, w, h);

                                if (clickRect.Contains(new SharpDX.Point(e.X, e.Y)))
                                {
                                    mi.MouseEntered = true;
                                    mi.MouseButton = e.Button;
                                    mi.MouseDownPoint = new PointF(e.X, e.Y);
                                    mi.MouseButtonDown = true;
                                    mi.MouseClick(e);
                                }
                                else
                                {
                                    mi.MouseEntered = false;
                                    mi.MouseButtonDown = false;
                                    mi.MouseClick(e);
                                }
                            }
                        }
                    }
                    HandledMouseEventArgs handledEventArgs = e as HandledMouseEventArgs;
                    if (handledEventArgs != null)
                    {
                        handledEventArgs.Handled = true;
                    }
                }
            }
            //private void OnClick(object sender, System.EventArgs e)
            //{
                
            //}
            private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
            {
                lock (_metersLock)
                {
                    PictureBox pb = sender as PictureBox;
                    if (pb == null) return;
                    string sId = pb.Tag.ToString();
                    if (!_meters.ContainsKey(sId)) return;

                    clsMeter m = _meters[sId];

                    lock (m._meterItemsLock)
                    {
                        if (m.SortedMeterItemsForZOrder != null)
                        {
                            float tw = targetWidth - 1f;// 0.5f;
                            float rw = m.XRatio;
                            float rh = m.YRatio;

                            SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                            foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                            {
                                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                float w = rect.Width * (mi.Size.Width / m.XRatio);
                                float h = rect.Height * (mi.Size.Height / m.YRatio);

                                SharpDX.RectangleF clickRect = new SharpDX.RectangleF(x, y, w, h);

                                if (clickRect.Contains(new SharpDX.Point(e.X, e.Y)))
                                {
                                    mi.MouseEntered = true;
                                    mi.MouseButton = e.Button;
                                    mi.MouseDownPoint = new PointF(e.X, e.Y);
                                    mi.MouseButtonDown = true;
                                    mi.MouseDown(e);
                                }
                                else
                                { 
                                    mi.MouseEntered = false;
                                    mi.MouseButtonDown = false;
                                    mi.MouseDown(e);
                                }
                            }
                        }
                    }
                    HandledMouseEventArgs handledEventArgs = e as HandledMouseEventArgs;
                    if (handledEventArgs != null)
                    {
                        handledEventArgs.Handled = true;
                    }
                }
            }
            private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
            {
                lock (_metersLock)
                {
                    PictureBox pb = sender as PictureBox;
                    if (pb == null) return;
                    string sId = pb.Tag.ToString();
                    if (!_meters.ContainsKey(sId)) return;

                    clsMeter m = _meters[sId];

                    lock (m._meterItemsLock)
                    {
                        if (m.SortedMeterItemsForZOrder != null)
                        {
                            float tw = targetWidth - 1f;// 0.5f;
                            float rw = m.XRatio;
                            float rh = m.YRatio;

                            SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                            foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                            {
                                if (mi.ItemType == clsMeterItem.MeterItemType.CLICKBOX)
                                {
                                    clsClickBox cb = (clsClickBox)mi;

                                    if ((!cb.OnlyWhenRX && !cb.OnlyWhenTX) || ((m.MOX && cb.OnlyWhenTX) || (!m.MOX && cb.OnlyWhenRX)))
                                    {
                                        float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                                        float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                        float w = rect.Width * (mi.Size.Width / m.XRatio);
                                        float h = rect.Height * (mi.Size.Height / m.YRatio);

                                        SharpDX.RectangleF clickRect = new SharpDX.RectangleF(x, y, w, h);

                                        if (clickRect.Contains(new SharpDX.Point(e.X, e.Y)))
                                        {
                                            m.MouseUp(e, m, cb);
                                        }
                                    }
                                }
                                else
                                {                                  
                                    float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                                    float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                    float w = rect.Width * (mi.Size.Width / m.XRatio);
                                    float h = rect.Height * (mi.Size.Height / m.YRatio);

                                    SharpDX.RectangleF clickRect = new SharpDX.RectangleF(x, y, w, h);

                                    if (clickRect.Contains(new SharpDX.Point(e.X, e.Y)))
                                    {
                                        mi.MouseButton = e.Button;
                                        mi.MouseUpPoint = new PointF(e.X, e.Y);
                                        mi.MouseButtonDown = false;
                                        mi.MouseUp(e);
                                    }
                                    else
                                    {
                                        mi.MouseButtonDown = false;
                                        mi.MouseUp(e);
                                    }
                                }
                            }
                        }
                    }
                    HandledMouseEventArgs handledEventArgs = e as HandledMouseEventArgs;
                    if (handledEventArgs != null)
                    {
                        handledEventArgs.Handled = true;
                    }
                }
            }

            //            
            private int drawMeters(out int height)
            {
                int nRedrawDelay = int.MaxValue;
                clsMeter m = _meter;

                height = ucMeter.MIN_CONTAINER_HEIGHT; // min height, taken from ucMeter

                lock (m._meterItemsLock)
                {
                    if (m.RX == _rx && m.SortedMeterItemsForZOrder != null && m.SortedMeterItemsForZOrder.Count > 0)
                    {
                        float tw = targetWidth - 1f;

                        int nTmp = m.MOX ? m.QuickestTXUpdate : m.QuickestRXUpdate;
                        if (nTmp < nRedrawDelay) nRedrawDelay = nTmp;

                        List<clsMeterItem> additionalDraws = new List<clsMeterItem>();
                        //foreach (KeyValuePair<string, clsMeterItem> mikvp in m.SortedMeterItemsForZOrder)
                        foreach (clsMeterItem mi in m.SortedMeterItemsForZOrder)
                        {
                            //clsMeterItem mi = mikvp.Value;

                            bool bRender = ((m.MOX && mi.OnlyWhenTX) || (!m.MOX && mi.OnlyWhenRX)) || (!mi.OnlyWhenTX && !mi.OnlyWhenRX);

                            if (bRender && ((m.DisplayGroup == 0 || mi.DisplayGroup == 0) || (mi.DisplayGroup == m.DisplayGroup)))
                            {
                                float rw = m.XRatio;
                                float rh = m.YRatio;

                                SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                                if (mi.ItemType == clsMeterItem.MeterItemType.ITEM_GROUP)
                                {
                                    float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                                    float h = rect.Height * (mi.Size.Height / m.YRatio);

                                    float w = rect.Width * (mi.Size.Width / m.XRatio);

                                    //bb.TopLeft = new PointF(_fPadX, fTop + _fPadY - (_fHeight * 0.75f));
                                    float padY = ((m.PadY - (m.Height * 0.75f)) * w);
                                    int hh = (int)Math.Ceiling((y + h + padY)) + 1; // 1 extra
                                    if (hh > height) height = hh;
                                }

                                switch (mi.ItemType)
                                {
                                    //case clsMeterItem.MeterItemType.V_BAR:
                                    //    renderVBar(rect, mi, m);
                                    //    break;
                                    case clsMeterItem.MeterItemType.H_BAR:
                                        clsMeterItem postDrawItem = renderHBar(rect, mi, m);
                                        if (postDrawItem != null)
                                            renderHBarMarkersOnly(rect, postDrawItem, m); // only draw the marker, this is so primary marker over the top of everything
                                                                                          // reason for this is that zorder for bar_pk and bar_avg causes the avg marker
                                                                                          // top be on top of the pk marker, which we dont want
                                        break;
                                    case clsMeterItem.MeterItemType.SOLID_COLOUR:
                                        renderSolidColour(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.NEEDLE:
                                        renderNeedle(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.IMAGE:
                                        renderImage(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.TEXT:
                                        renderText(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.H_SCALE:
                                    case clsMeterItem.MeterItemType.V_SCALE:
                                        renderScale(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.NEEDLE_SCALE_PWR:
                                        renderNeedleScale(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.MAGIC_EYE:
                                        renderEye(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.SPACER:
                                        renderSpacer(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.WEB_IMAGE:
                                        renderWebImage(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.TEXT_OVERLAY:
                                        renderTextOverlay(rect, mi, m, false);
                                        additionalDraws.Add(mi);
                                        break;
                                    case clsMeterItem.MeterItemType.LED:
                                        renderLed(rect, mi, m, false);
                                        additionalDraws.Add(mi);
                                        break;
                                    case clsMeterItem.MeterItemType.DATA_OUT:
                                        break;
                                    case clsMeterItem.MeterItemType.ROTATOR:
                                        renderRotator(rect, mi, m);
                                        break;
                                    //case clsMeterItem.MeterItemType.ITEM_GROUP:
                                    //    renderGroup(rect, mi, m);
                                    //    break;
                                    case clsMeterItem.MeterItemType.VFO_DISPLAY:
                                        renderVfoDisplay(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.CLOCK:
                                        renderClock(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.SIGNAL_TEXT_DISPLAY:
                                        renderSignalTextDisplay(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.TUNESTEP_BUTTONS:
                                    case clsMeterItem.MeterItemType.ANTENNA_BUTTONS:
                                    case clsMeterItem.MeterItemType.FILTER_BUTTONS:
                                    case clsMeterItem.MeterItemType.MODE_BUTTONS:
                                    case clsMeterItem.MeterItemType.BAND_BUTTONS:
                                    case clsMeterItem.MeterItemType.DISCORD_BUTTONS:
                                        renderButtonBox(rect, mi, m);
                                        break;                                    
                                    //case clsMeterItem.MeterItemType.SPECTRUM:
                                    //    renderSpectrum(rect, mi, m);
                                    //    break;
                                    case clsMeterItem.MeterItemType.FADE_COVER:
                                        renderFadeCover(rect, mi, m);
                                        break;
                                    case clsMeterItem.MeterItemType.HISTORY:
                                        renderHistory(rect, mi, m);
                                        break;
                                }
                            }
                        }
                        
                        foreach(clsMeterItem mi in additionalDraws)
                        {                           
                            float rw = m.XRatio;
                            float rh = m.YRatio;

                            SharpDX.RectangleF rect = new SharpDX.RectangleF(0, 0, tw * rw, tw * rh);

                            switch (mi.ItemType)
                            {
                                case clsMeterItem.MeterItemType.TEXT_OVERLAY:
                                    bool scrolling = renderTextOverlay(rect, mi, m, true);
                                    if (scrolling && nRedrawDelay > 32) nRedrawDelay = 32; // ensure decent redraw if scroll text (30fps)
                                    break;
                                case clsMeterItem.MeterItemType.LED:
                                    renderLed(rect, mi, m, true);
                                    break;
                            }
                        }
                    }
                }
                return nRedrawDelay;
            }
            private SizeF measureString(string sText, string sFontFamily, FontStyle style, float emSize, bool ignore_caching = false)
            {
                if (!_bDXSetup) return SizeF.Empty;                
                if (string.IsNullOrEmpty(sText)) return SizeF.Empty;
                emSize = (float)Math.Round(emSize, 2);
                if (emSize == 0) return SizeF.Empty; // zero size text is zero measurement

                string sKey = sFontFamily + "_" + style + "_" + sText + "_" + emSize.ToString("0.00");

                if (!ignore_caching && _stringMeasure.ContainsKey(sKey)) return _stringMeasure[sKey];

                SharpDX.DirectWrite.FontWeight fontWeight = SharpDX.DirectWrite.FontWeight.Regular;
                SharpDX.DirectWrite.FontStyle fontStyle = SharpDX.DirectWrite.FontStyle.Normal;
                if (((int)style & (int)FontStyle.Bold) == (int)FontStyle.Bold) fontWeight = SharpDX.DirectWrite.FontWeight.Bold;
                if (((int)style & (int)FontStyle.Italic) == (int)FontStyle.Italic) fontStyle = SharpDX.DirectWrite.FontStyle.Italic;

                // calculate how big the string would be @ emSize pt
                SharpDX.DirectWrite.TextFormat tf = new SharpDX.DirectWrite.TextFormat(_fontFactory, sFontFamily, fontWeight, fontStyle, emSize);
                tf.WordWrapping = SharpDX.DirectWrite.WordWrapping.NoWrap;
                
                SharpDX.DirectWrite.TextLayout layout = new SharpDX.DirectWrite.TextLayout(_fontFactory, sText, tf, float.MaxValue, float.MaxValue);
                float width = layout.Metrics.Width;
                float height = layout.Metrics.Height;
                Utilities.Dispose(ref layout);
                layout = null;
                Utilities.Dispose(ref tf);
                tf = null;

                SizeF size = new SizeF(width, height);

                //this is a 96 to 72 scaling factor for text to screen dpi
                size.Width *= _renderTarget.DotsPerInch.Width / 72f;
                size.Height *= _renderTarget.DotsPerInch.Height / 72f;

                bool bAdd = true;
                if (ignore_caching)
                {
                    // try to update existing
                    if(_stringMeasure.ContainsKey(sKey))
                    {
                        SizeF f = _stringMeasure[sKey];
                        f.Width = size.Width;
                        f.Height = size.Height;
                        _stringMeasure[sKey] = f;
                        bAdd = false;
                    }
                }
                if (bAdd)
                {
                    // keys are also stored in a queue, this ensures order, oldest get removed first
                    _stringMeasure.Add(sKey, size);
                    _stringMeasureKeys.Enqueue(sKey);
                    if (_stringMeasure.Count > 2000)
                    {
                        string oldKey = _stringMeasureKeys.Dequeue();
                        _stringMeasure.Remove(oldKey);
                    }
                }

                return size;
            }
            // [2.10.1.0] MW0LGE
            private int fade(clsMeterItem mi, clsMeter m)
            {
                int dimmed = 48; // the level to dim to
                if (!mi.Disabled && !mi.FadeOnTx && m.MOX && mi.FadeValue == 255) return 255;
                if (!mi.Disabled && !mi.FadeOnRx && !m.MOX && mi.FadeValue == 255) return 255;

                int updateInterval = m.QuickestUpdateInterval(m.MOX);
                updateInterval = Math.Min(updateInterval, 500);
                // fade to take half a second
                int steps_needed = (int)Math.Ceiling(500 / (float)updateInterval);
                int stepSize = (int)Math.Ceiling(207 / (float)steps_needed); // 255-48 = 207

                if (mi.Disabled || (m.MOX && mi.FadeOnTx) || (!m.MOX && mi.FadeOnRx))
                {
                    mi.FadeValue -= stepSize;
                    if (mi.FadeValue < dimmed) mi.FadeValue = dimmed;
                }
                else
                {
                    mi.FadeValue += stepSize;
                    if (mi.FadeValue > 255) mi.FadeValue = 255;
                }

                return mi.FadeValue;
            }
            //
            private void renderNeedleScale(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsNeedleScalePwrItem scale = (clsNeedleScalePwrItem)mi;

                if (scale.ScaleCalibration == null || scale.ScaleCalibration.Count == 0) return;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                SharpDX.RectangleF mirect = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.DrawRectangle(mirect, getDXBrushForColour(System.Drawing.Color.CornflowerBlue));

                if (scale.ItemType == clsMeterItem.MeterItemType.NEEDLE_SCALE_PWR && (scale.ReadingSource == Reading.PWR || scale.ReadingSource == Reading.REVERSE_PWR))
                {
                    // go through subset of scalecalibration items to get positions for text       (10 for ananmm, 15 for crosspwr, 19 for crosspwrref )  7 8 8          
                    float fontSizeEmScaled = (scale.FontSize / 16f) * (rect.Width / 52f);
                    SizeF szTextSize;
                    int nMaxIndex = -1;

                    for (int n = 0; n < scale.Marks; n++)
                    {
                        int index = 0;
                        float fChangeAngle = 0;

                        if (scale.ScaleCalibration.Count == 10) // the ananmm
                        {
                            switch (n)
                            {
                                case 0: //0
                                    index = 0;
                                    fChangeAngle = (float)degToRad(-11);
                                    break;
                                case 1: //5
                                    index = 1;
                                    fChangeAngle = (float)degToRad(8);
                                    break;
                                case 2: //10
                                    index = 2;
                                    fChangeAngle = (float)degToRad(10);
                                    break;
                                case 3: //25
                                    index = 3;
                                    fChangeAngle = (float)degToRad(5);
                                    break;
                                case 4: //50
                                    index = 6;
                                    break;
                                case 5: //100
                                    index = 8;
                                    fChangeAngle = (float)degToRad(-8);
                                    break;
                                case 6: //150
                                    index = 9;
                                    fChangeAngle = (float)degToRad(-10);
                                    nMaxIndex = index;
                                    break;
                            }
                        }
                        else if (scale.ScaleCalibration.Count == 15) // the cross pwr
                        {
                            switch (n)
                            {
                                case 0: //0
                                    index = 0;
                                    break;
                                case 1: //10
                                    index = 2;
                                    break;
                                case 2: //20
                                    index = 4;
                                    break;
                                case 3: //30
                                    index = 6;
                                    break;
                                case 4: //40
                                    index = 8;
                                    break;
                                case 5: //70
                                    index = 11;
                                    break;
                                case 6: //80
                                    index = 12;
                                    break;
                                case 7: //100
                                    index = 14;
                                    nMaxIndex = index;
                                    break;
                            }
                        }
                        else if (scale.ScaleCalibration.Count == 19) // the cross ref
                        {
                            switch (n)
                            {
                                case 0: //0
                                    index = 0;
                                    break;
                                case 1: //1
                                    index = 4;
                                    break;
                                case 2: //2
                                    index = 5;
                                    break;
                                case 3: //4
                                    index = 7;
                                    break;
                                case 4: //6
                                    index = 9;
                                    break;
                                case 5: //8
                                    index = 11;
                                    break;
                                case 6: //16
                                    index = 16;
                                    break;
                                case 7: //20
                                    index = 18;
                                    nMaxIndex = index;
                                    break;
                            }
                        }

                        Dictionary<float, PointF>.KeyCollection kc = mi.ScaleCalibration.Keys;
                        int key = 0;
                        foreach (float k in kc)
                        {
                            if (index == key)
                            {
                                getPerc(mi, k, out float percX, out float percY, out PointF min, out PointF max);

                                //offset from centre
                                float cX = x + (w / 2);
                                float cY = y + (h / 2);
                                float startX = cX + (w * scale.NeedleOffset.X);
                                float startY = cY + (h * scale.NeedleOffset.Y);

                                float rotation = 180f;

                                float radiusX = (w / 2) * (scale.LengthFactor * scale.RadiusRatio.X);
                                float radiusY = (w / 2) * (scale.LengthFactor * scale.RadiusRatio.Y);

                                //todo
                                switch (scale.Placement)
                                {
                                    case clsNeedleItem.NeedlePlacement.Bottom:
                                        rotation = 180f;
                                        break;
                                    case clsNeedleItem.NeedlePlacement.Left:
                                        //rotation = 90f;
                                        break;
                                    case clsNeedleItem.NeedlePlacement.Top:
                                        //rotation = 0f;
                                        break;
                                    case clsNeedleItem.NeedlePlacement.Right:
                                        //rotation = 270f;
                                        break;
                                }

                                float eX, eY, dX, dY;

                                // map the meter scales to pixels
                                eX = x + (min.X * w) + (percX * ((max.X - min.X) * w));
                                eY = y + (min.Y * h) + (percY * ((max.Y - min.Y) * h));

                                // calc angle required
                                dX = startX - eX;
                                dY = startY - eY;
                                // expand
                                dX /= scale.RadiusRatio.X;
                                dY /= scale.RadiusRatio.Y;
                                float ang = (float)Math.Atan2(dY, dX);

                                float endX = startX + (float)(Math.Cos(ang + degToRad(rotation)) * radiusX);
                                float endY = startY + (float)(Math.Sin(ang + degToRad(rotation)) * radiusY);

                                float fPower = k * (scale.MaxPower / 100f);
                                bool bmW = scale.MaxPower <= 1f; // switch to mW if sub 1 watt
                                if (bmW) fPower *= 1000f;

                                string sText = tidyPower(fPower);
                                if (index == nMaxIndex) sText += bmW ? "mW" : "W"; // last index, add W or mW

                                szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, fontSizeEmScaled);

                                float fontEndX = endX - (szTextSize.Width / 2f);
                                float fontEndY = endY - (szTextSize.Height / 2f);

                                Matrix3x2 currentTransform = _renderTarget.Transform;

                                Matrix3x2 t = Matrix3x2.Rotation((ang + fChangeAngle) + (float)(degToRad(90f + rotation))/*(float)radToDeg(ang) + rotation*/, new Vector2(endX, endY));
                                t.TranslationVector += _pixelShift;
                                _renderTarget.Transform = t;

                                SharpDX.RectangleF txtrect = new SharpDX.RectangleF(fontEndX, fontEndY, szTextSize.Width, szTextSize.Height);
                                _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, fontSizeEmScaled, scale.FntStyle), txtrect, getDXBrushForColour(scale.LowColour, 255));

                                _renderTarget.Transform = currentTransform;

                                break;
                            }
                            key++;
                        }
                    }
                }
            }
            private string tidyPower(float fPower)
            {
                float fRemainder = fPower - (int)Math.Floor(fPower);
                string sFormat = "f0";
                if (fRemainder >= 0.1f && fPower <= 8f) sFormat = "f1";

                return fPower.ToString(sFormat);
            }
            private void renderScale(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsScaleItem scale = (clsScaleItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                SharpDX.RectangleF mirect = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.DrawRectangle(mirect, getDXBrushForColour(System.Drawing.Color.CornflowerBlue));

                RawVector2 startPoint = new RawVector2();
                RawVector2 endPoint = new RawVector2();

                if (scale.ItemType == clsMeterItem.MeterItemType.H_SCALE)
                {
                    float fontSizeEmScaled = (scale.FontSize / 16f) * (rect.Width / 52f);
                    SizeF szTextSize;

                    if (scale.ShowType)
                    {
                        // meter type text
                        string sText = scale.ReadingName;//MeterManager.ReadingName(scale.ReadingSource);
                        szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, fontSizeEmScaled);
                        SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x + (w * 0.5f) - (szTextSize.Width / 2f), y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                        _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, fontSizeEmScaled, scale.FntStyle), txtrect, getDXBrushForColour(scale.FontColourType, 255));
                    }

                    //szTextSize = measureString("0", scale.FontFamily, scale.FntStyle, fontSizeEmScaled);

                    float fLineBaseY = y + (h * 0.85f);

                    switch (scale.ReadingSource)
                    {
                        case Reading.AGC_GAIN:
                            {
                                generalScale(x, y, w, h, scale, 7, 1, -50, 125, 25, 25, fLineBaseY, fontSizeEmScaled, 255);
                            }
                            break;
                        case Reading.AGC_AV:
                        case Reading.AGC_PK:
                            {
                                generalScale(x, y, w, h, scale, 6, 5, -125, 125, 25, 25, fLineBaseY, fontSizeEmScaled, 255);
                            }
                            break;
                        case Reading.SIGNAL_STRENGTH:
                        case Reading.AVG_SIGNAL_STRENGTH:
                            {
                                generalScale(x,y,w,h, scale, 6, 3, -1, 60, 2, 20, fLineBaseY, fontSizeEmScaled, 255, 0.5f, true, true);
                            }
                            break;
                        case Reading.ADC_PK:
                        case Reading.ADC_AV:
                            {
                                generalScale(x, y, w, h, scale, 6, 1, -120, 0, 20, 20, fLineBaseY, fontSizeEmScaled, 255);
                            }
                            break;
                        case Reading.ESTIMATED_PBSNR:
                            {
                                generalScale(x, y, w, h, scale, 6, 1, 0, 60, 10, 10, fLineBaseY, fontSizeEmScaled, 255);
                            }
                            break;
                        // -30 to 12
                        case Reading.MIC:
                        case Reading.MIC_PK:
                        case Reading.EQ:
                        case Reading.EQ_PK:
                        case Reading.LEVELER:
                        case Reading.LEVELER_PK:
                        case Reading.CFC_AV:
                        case Reading.CFC_PK:
                        case Reading.COMP:
                        case Reading.COMP_PK:
                        //case Reading.CPDR: //CPDR is the same as comp
                        //case Reading.CPDR_PK: //CPDR is the same as comp
                        case Reading.ALC:
                        case Reading.ALC_PK:
                            {
                                generalScale(x, y, w, h, scale, 4, 3, -30, 12, 10, 4, fLineBaseY, fontSizeEmScaled, 255, 0.665f);
                            }
                            break;
                        case Reading.ALC_GROUP:
                            {
                                generalScale(x, y, w, h, scale, 4, 5, -30, 25, 10, 5, fLineBaseY, fontSizeEmScaled, 255, 0.5f, true);
                            }
                            break;
                        case Reading.PWR:
                        case Reading.REVERSE_PWR:
                            {
                                //string[] list500 = { "50", "100", "250", "500", "600+" };
                                //string[] list200 = { "10", "20", "100", "200", "240+" };
                                //string[] list100 = { "5", "10", "50", "100", "120+" };
                                //string[] list30 = { "5", "10", "20", "30", "50+" };
                                //string[] list15 = { "1", "5", "10", "15", "25+" };
                                //string[] list1 = { "100", "250", "500", "800", "1000+" };

                                //int nPower = MeterManager.CurrentPowerRating;
                                //string[] powerList;
                                //switch (nPower)
                                //{
                                //    case 500:
                                //        powerList = list500;
                                //        break;
                                //    case 200:
                                //        powerList = list200;
                                //        break;
                                //    case 100:
                                //        powerList = list100;
                                //        break;
                                //    case 30:
                                //        powerList = list30;
                                //        break;
                                //    case 15:
                                //        powerList = list15;
                                //        break;
                                //    case 1:
                                //        powerList = list1;
                                //        break;
                                //    default:
                                //        powerList = list500;
                                //        break;
                                //}

                                SharpDX.Direct2D1.Brush lowColour = getDXBrushForColour(scale.LowColour, 255);
                                SharpDX.Direct2D1.Brush highColour = getDXBrushForColour(scale.HighColour, 255);

                                string[] powerList = new string[5];
                                float fMaxPower = scale.MaxPower <= 1f ? scale.MaxPower * 1000f : scale.MaxPower;

                                // scaled to 100w
                                powerList[0] = tidyPower(fMaxPower * (5 / 100f));
                                powerList[1] = tidyPower(fMaxPower * (10 / 100f));
                                powerList[2] = tidyPower(fMaxPower * (50 / 100f));
                                powerList[3] = tidyPower(fMaxPower * (100 / 100f));
                                powerList[4] = tidyPower(fMaxPower * (120 / 100f)) + "+";

                                float spacing = (w * 0.75f) / 4.0f;

                                // horiz line
                                startPoint.X = x;
                                startPoint.Y = fLineBaseY;
                                endPoint.X = x + (w * 0.75f);
                                endPoint.Y = startPoint.Y;
                                _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                                startPoint.X = endPoint.X;
                                endPoint.X = x + (w * 0.99f);
                                _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);

                                //low markers
                                for (int i = 1; i < 5; i++)
                                {
                                    // short ticks
                                    startPoint.X = x + (i * spacing) - (spacing * 0.5f); // - half a space to shift left between longer ticks
                                    endPoint.X = startPoint.X;
                                    endPoint.Y = fLineBaseY - (h * 0.15f);

                                    _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                                    // long ticks
                                    startPoint.X = x + (i * spacing);
                                    endPoint.X = startPoint.X;
                                    endPoint.Y = fLineBaseY - (h * 0.3f);

                                    _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                                    // text
                                    string sText = powerList[i - 1];
                                    szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, fontSizeEmScaled);
                                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(startPoint.X - (szTextSize.Width * 0.5f), endPoint.Y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, fontSizeEmScaled, scale.FntStyle), txtrect, lowColour);
                                }

                                //high markers
                                spacing = ((w * 0.99f) - (w * 0.75f));

                                // short ticks
                                startPoint.X = x + (w * 0.75f) + (1 * spacing) - (spacing * 0.5f); // - half a space to shift left between longer ticks
                                endPoint.X = startPoint.X;
                                endPoint.Y = fLineBaseY - (h * 0.15f);

                                _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);

                                // long ticks
                                startPoint.X = x + (w * 0.75f) + (1 * spacing);
                                endPoint.X = startPoint.X;
                                endPoint.Y = fLineBaseY - (h * 0.3f);

                                _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);

                                // text
                                string sText2 = powerList[4];
                                szTextSize = measureString(sText2, scale.FontFamily, scale.FntStyle, fontSizeEmScaled);
                                SharpDX.RectangleF txtrect2 = new SharpDX.RectangleF(startPoint.X - szTextSize.Width, endPoint.Y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                                _renderTarget.DrawText(sText2, getDXTextFormatForFont(scale.FontFamily, fontSizeEmScaled, scale.FntStyle), txtrect2, highColour);
                            }
                            break;
                        //case MeterTXMode.SWR_POWER:
                        //    {

                        //    }
                        //    break;
                        case Reading.SWR:
                            {
                                SharpDX.Direct2D1.Brush lowColour = getDXBrushForColour(scale.LowColour, 255);
                                SharpDX.Direct2D1.Brush highColour = getDXBrushForColour(scale.HighColour, 255);

                                float spacing = (w * 0.75f) / 15.0f;
                                string[] swr_list = { "1.5", "2" };
                                string[] swr_hi_list = { "3", "4", "5" };

                                //g.FillRectangle(low_brush, 0, H - 4, (int)(W * 0.75), 2);
                                //g.FillRectangle(high_brush, (int)(W * 0.75), H - 4, (int)(W * 0.25) - 4, 2);

                                // horiz line
                                startPoint.X = x;
                                startPoint.Y = fLineBaseY;
                                endPoint.X = x + (w * 0.75f);
                                endPoint.Y = startPoint.Y;
                                _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                                startPoint.X = endPoint.X;
                                endPoint.X = x + (w * 0.99f);
                                _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);

                                //low markers
                                for (int i = 1; i < 15; i++)
                                {
                                    // short ticks
                                    startPoint.X = x + (i * spacing);
                                    endPoint.X = startPoint.X;
                                    endPoint.Y = fLineBaseY - (h * 0.15f);

                                    _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);
                                }

                                //low markers
                                for (int i = 1; i < 15; i++)
                                {
                                    // short ticks
                                    startPoint.X = x + (i * spacing);
                                    endPoint.X = startPoint.X;
                                    endPoint.Y = fLineBaseY - (h * 0.15f);

                                    _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);
                                }
                                for (int i = 1; i < 3; i++)
                                {
                                    spacing = (w * 0.5f) / 2.0f;

                                    // long ticks 
                                    startPoint.X = x + (i * spacing);
                                    endPoint.X = startPoint.X;
                                    endPoint.Y = fLineBaseY - (h * 0.3f);

                                    _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                                    // text
                                    string sText = swr_list[i - 1];
                                    szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, fontSizeEmScaled);
                                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(startPoint.X - (szTextSize.Width / 2f), endPoint.Y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, fontSizeEmScaled, scale.FntStyle), txtrect, lowColour);
                                }

                                //high markers
                                spacing = ((w * 0.99f) - (w * 0.75f)) / 2f;

                                for (int i = 1; i < 4; i++)
                                {
                                    if (i < 3) // as small tick will go off then end when i=3
                                    {
                                        // short ticks
                                        startPoint.X = x + (w * 0.75f) + (i * spacing) - (spacing * 0.5f); // - half a space to shift left between longer ticks
                                        endPoint.X = startPoint.X;
                                        endPoint.Y = fLineBaseY - (h * 0.15f);

                                        _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);
                                    }

                                    // long ticks 
                                    startPoint.X = x + (w * 0.75f) + (i * spacing) - spacing; // - one full space to the left
                                    endPoint.X = startPoint.X;
                                    endPoint.Y = fLineBaseY - (h * 0.3f);

                                    _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);

                                    // text
                                    string sText = swr_hi_list[i - 1];
                                    szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, fontSizeEmScaled);
                                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(startPoint.X - (szTextSize.Width / 2f), endPoint.Y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, fontSizeEmScaled, scale.FntStyle), txtrect, highColour);
                                }
                            }
                            break;
                        case Reading.ALC_G:
                        case Reading.LVL_G:
                        case Reading.CFC_G:
                            {
                                generalScale(x, y, w, h, scale, 5, 1, 0, 25, 5, 5, fLineBaseY, fontSizeEmScaled, 255, -1, true);                                
                            }
                            break;
                        default:
                            break;
                    }
                }
                else if (scale.ItemType == clsMeterItem.MeterItemType.V_SCALE)
                {
                    float fontSize = 8f;
                    SizeF adjustedFontSize = measureString("0", scale.FontFamily, scale.FntStyle, fontSize);
                    float ratio = w / adjustedFontSize.Width;
                    float newSize = (float)Math.Round((fontSize * ratio) * (fontSize / _renderTarget.DotsPerInch.Width), 1);

                    if (scale.ShowType)
                    {
                        string sText = scale.ReadingName;//MeterManager.ReadingName(scale.ReadingSource);
                        adjustedFontSize = measureString(sText, scale.FontFamily, scale.FntStyle, newSize);
                        SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x, y - (adjustedFontSize.Height * 1.1f), adjustedFontSize.Width, adjustedFontSize.Height);
                        _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, newSize, scale.FntStyle), txtrect, getDXBrushForColour(scale.FontColourType, 255));
                    }

                    fontSize = 10f;
                    adjustedFontSize = measureString("0", scale.FontFamily, scale.FntStyle, fontSize);
                    ratio = w / adjustedFontSize.Width;
                    newSize = (float)Math.Round((fontSize * ratio) * (fontSize / _renderTarget.DotsPerInch.Width), 1);

                    float fBottomY = y + h;
                    float xCentreLine = x + (w * 0.5f);                    

                    switch (scale.ReadingSource)
                    {
                        case Reading.SIGNAL_STRENGTH:
                        case Reading.AVG_SIGNAL_STRENGTH:
                            {
                                float spacing = (h * 0.5f) / 5.0f;
                                if (scale.ShowMarkers)
                                {
                                    // vert line
                                    startPoint.X = xCentreLine;
                                    startPoint.Y = fBottomY;
                                    endPoint.X = xCentreLine;
                                    endPoint.Y = fBottomY - (h * 0.5f);
                                    _renderTarget.DrawLine(startPoint, endPoint, getDXBrushForColour(scale.LowColour, 255), 2f);

                                    startPoint.Y = endPoint.Y;
                                    endPoint.Y = fBottomY - (h * 0.99f);
                                    _renderTarget.DrawLine(startPoint, endPoint, getDXBrushForColour(scale.HighColour, 255), 2f);
                                }

                                // markers low                                
                                for (int i = 1; i < 6; i++)
                                {
                                    if (scale.ShowMarkers)
                                    {
                                        // short ticks
                                        startPoint.X = xCentreLine - (w * 0.1f);
                                        startPoint.Y = fBottomY - ((i * spacing) - (spacing * 0.5f)); // - half a space to shift left between longer ticks
                                        endPoint.X = xCentreLine + (w * 0.1f);
                                        endPoint.Y = startPoint.Y;

                                        _renderTarget.DrawLine(startPoint, endPoint, getDXBrushForColour(scale.LowColour, 255), 2f);
                                    }

                                    // long ticks
                                    startPoint.X = xCentreLine - (w * 0.3f);
                                    startPoint.Y = fBottomY - (i * spacing);
                                    endPoint.X = xCentreLine + (w * 0.3f);
                                    endPoint.Y = startPoint.Y;

                                    if (scale.ShowMarkers) _renderTarget.DrawLine(startPoint, endPoint, getDXBrushForColour(scale.LowColour, 255), 2f);

                                    // text
                                    string sText = (-1 + i * 2).ToString();
                                    adjustedFontSize = measureString(sText, scale.FontFamily, scale.FntStyle, newSize);
                                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(endPoint.X + (w * 0.08f), endPoint.Y - (adjustedFontSize.Height / 2f), adjustedFontSize.Width, adjustedFontSize.Height);
                                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, newSize, scale.FntStyle), txtrect, getDXBrushForColour(scale.FontColourLow, 255));
                                }

                                // markers high
                                spacing = ((h * 0.5f) - (h * 0.01f)) / 3.0f; // - w*0.01f as we only draw the line up to w*0.99f
                                for (int i = 1; i < 4; i++)
                                {

                                    //short ticks
                                    if (scale.ShowMarkers)
                                    {
                                        startPoint.X = xCentreLine - (w * 0.1f);
                                        startPoint.Y = fBottomY -  ((h * 0.5f) + (i * spacing) - (spacing * 0.5f)); // - half a space to shift left between longer ticks
                                        endPoint.X = xCentreLine + (w * 0.1f);
                                        endPoint.Y = startPoint.Y;

                                        _renderTarget.DrawLine(startPoint, endPoint, getDXBrushForColour(scale.HighColour, 255), 2f);
                                    }

                                    // long ticks
                                    startPoint.X = xCentreLine - (w * 0.3f);
                                    startPoint.Y = fBottomY - ((h * 0.5f) + (i * spacing));
                                    endPoint.X = xCentreLine + (w * 0.3f);
                                    endPoint.Y = startPoint.Y;

                                    if (scale.ShowMarkers) _renderTarget.DrawLine(startPoint, endPoint, getDXBrushForColour(scale.HighColour, 255), 2f);

                                    // text
                                    string sText = "+" + (i * 20).ToString();
                                    adjustedFontSize = measureString(sText, scale.FontFamily, scale.FntStyle, newSize);
                                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(endPoint.X - (w * 0.2f), endPoint.Y - (adjustedFontSize.Height / 2f) + (h * 0.01f), adjustedFontSize.Width, adjustedFontSize.Height);
                                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, newSize, scale.FntStyle), txtrect, getDXBrushForColour(scale.FontColourHigh, 255));
                                }
                            }
                            break;
                    }
                }
            }
            private void generalScale(float x,float y,float w,float h,clsScaleItem scale, int lowLongTicks, int highLongTicks, int lowStartNumner, int highEndNumber, int lowIncrement, int highIngrement, float fLineBaseY, float newSize, int nFade, float centrePerc = -1, bool addTrailingPlus = false, bool addAllTrailingPlus = false)
            {
                float lowToHighPoint = (float)(lowLongTicks - 1) / (float)(lowLongTicks + highLongTicks - 1);

                if (centrePerc >= 0) lowToHighPoint = centrePerc;

                float spacing = (w * lowToHighPoint) / (float)(lowLongTicks - 1);//(w * 0.6666f) / 5.0f;
                RawVector2 startPoint = new RawVector2();
                RawVector2 endPoint = new RawVector2();

                SharpDX.Direct2D1.Brush lowColour = getDXBrushForColour(scale.LowColour, nFade);
                SharpDX.Direct2D1.Brush fontLowColour = getDXBrushForColour(scale.FontColourLow, nFade);
                SharpDX.Direct2D1.Brush highColour = getDXBrushForColour(scale.HighColour, nFade);
                SharpDX.Direct2D1.Brush fontHighColour = getDXBrushForColour(scale.FontColourHigh, nFade);

                if (scale.ShowMarkers)
                {
                    // horiz line
                    startPoint.X = x;
                    startPoint.Y = fLineBaseY;
                    endPoint.X = x + (spacing * (float)(lowLongTicks - 1));//(w * 0.6666f);
                    endPoint.Y = startPoint.Y;
                    _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                    startPoint.X = endPoint.X;
                    endPoint.X = x + (w * 0.99f);
                    _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);
                }

                // markers low                                
                for (int i = 1; i < lowLongTicks; i++)
                {
                    if (scale.ShowMarkers)
                    {
                        // short ticks
                        startPoint.X = x + (i * spacing) - (spacing * 0.5f); // - half a space to shift left between longer ticks
                        endPoint.X = startPoint.X;
                        endPoint.Y = fLineBaseY - (h * 0.15f);

                        _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);
                    }

                    // long ticks
                    startPoint.X = x + (i * spacing);
                    endPoint.X = startPoint.X;
                    endPoint.Y = fLineBaseY - (h * 0.3f);

                    if (scale.ShowMarkers) _renderTarget.DrawLine(startPoint, endPoint, lowColour, 2f);

                    // text
                    string sText = (lowStartNumner + i * lowIncrement).ToString();
                    SizeF szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, newSize);
                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(startPoint.X - (szTextSize.Width / 2f), endPoint.Y - szTextSize.Height, szTextSize.Width, szTextSize.Height);
                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, newSize, scale.FntStyle), txtrect, fontLowColour);
                }
                
                // markers high
                float lowSpacing = spacing;
                spacing = ((w - (lowSpacing * (float)(lowLongTicks - 1))) - (w * 0.01f)) / (float)highLongTicks; // - w*0.01f as we only draw the line up to w*0.99f
                for (int i = 1; i < highLongTicks + 1; i++)
                {

                    //short ticks
                    if (scale.ShowMarkers)
                    {
                        startPoint.X = x + (lowSpacing * (float)(lowLongTicks-1)) + (i * spacing) - (spacing * 0.5f); // - half a space to shift left between longer ticks
                        endPoint.X = startPoint.X;
                        endPoint.Y = fLineBaseY - (h * 0.15f);

                        _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);
                    }

                    // long ticks
                    startPoint.X = x + ((lowSpacing * (float)(lowLongTicks - 1))) + (i * spacing);
                    endPoint.X = startPoint.X;
                    endPoint.Y = fLineBaseY - (h * 0.3f);

                    if (scale.ShowMarkers) _renderTarget.DrawLine(startPoint, endPoint, highColour, 2f);

                    // text
                    string sText = ((highEndNumber - (highLongTicks * highIngrement)) + i * highIngrement).ToString();
                    if (addAllTrailingPlus || (i == highLongTicks && addTrailingPlus)) sText += "+";
                    SizeF szTextSize = measureString(sText, scale.FontFamily, scale.FntStyle, newSize);
                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(i == highLongTicks ? x + w - szTextSize.Width : startPoint.X - szTextSize.Width / 2f, endPoint.Y - szTextSize.Height, szTextSize.Width, szTextSize.Height);
                    _renderTarget.DrawText(sText, getDXTextFormatForFont(scale.FontFamily, newSize, scale.FntStyle), txtrect, fontHighColour);
                }
            }
            private void renderGroup(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsItemGroup ig = (clsItemGroup)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                SharpDX.RectangleF igrect = new SharpDX.RectangleF(x, y, w, h);
                _renderTarget.DrawRectangle(igrect, getDXBrushForColour(System.Drawing.Color.Green));

                float newSize = 16f;
                string sText = ig.Order.ToString();
                SizeF szTextSize = measureString(sText, "Trebuchet MS", FontStyle.Regular, newSize);
                SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x, y, szTextSize.Width, szTextSize.Height);
                _renderTarget.DrawText(sText, getDXTextFormatForFont("Trebuchet MS", newSize, FontStyle.Regular), txtrect, getDXBrushForColour(System.Drawing.Color.White));
            }
            private void slits(Vector2 centre, float radiusX, float radiusY, float w, float h, SharpDX.Direct2D1.Brush closedSectionBrush)
            {
                const double rads90 = Math.PI / 2f;

                // do the slits slits either side
                PathGeometry sharpGeometry = new PathGeometry(_renderTarget.Factory);
                GeometrySink geo = sharpGeometry.Open();
                //start bottom
                geo.BeginFigure(new SharpDX.Vector2(centre.X, centre.Y + (h * 0.03f)), FigureBegin.Filled);

                // calc radius point right side
                float rX = centre.X + (float)Math.Sin(rads90) * radiusX;
                float rY = centre.Y + (float)Math.Cos(rads90) * radiusY;
                geo.AddLine(new SharpDX.Vector2(rX, rY));

                //to top
                geo.AddLine(new SharpDX.Vector2(centre.X, centre.Y - (h * 0.03f)));

                // calc radius point left side
                rX = centre.X + (float)Math.Sin(-rads90) * radiusX;
                rY = centre.Y + (float)Math.Cos(-rads90) * radiusY;
                geo.AddLine(new SharpDX.Vector2(rX, rY));

                geo.EndFigure(FigureEnd.Closed); // adds the closing line
                geo.Close();

                _renderTarget.FillGeometry(sharpGeometry, closedSectionBrush);

                Utilities.Dispose(ref geo);
                geo = null;
                Utilities.Dispose(ref sharpGeometry);
                sharpGeometry = null;
            }
            private void renderLed(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m, bool draw_led)
            {
                clsLed led = (clsLed)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                bool do_cover_fade = (mi.FadeOnRx && !m.MOX) || (mi.FadeOnTx && m.MOX);
                if (!do_cover_fade && (led.PanelBackColour1 != led.PanelBackColour2))
                {
                    if (m.MOX != mi.MOXFadeToggle)
                    {
                        mi.FadeValue = 48;
                        mi.MOXFadeToggle = m.MOX;
                    }
                    else
                    {
                        int updateInterval = m.QuickestUpdateInterval(m.MOX);
                        updateInterval = Math.Min(updateInterval, 500);
                        // fade to take half a second
                        int steps_needed = (int)Math.Ceiling(500 / (float)updateInterval);
                        int stepSize = (int)Math.Ceiling(207 / (float)steps_needed); // 255-48 = 207

                        mi.FadeValue += stepSize;
                        if (mi.FadeValue > 255) mi.FadeValue = 255;
                    }
                }

                if (!draw_led)
                {
                    if (led.ShowBackPanel)
                    {
                        SharpDX.RectangleF rectSC = new SharpDX.RectangleF(x, y, w, h);
                        _renderTarget.FillRectangle(rectSC, getDXBrushForColour(m.MOX ? led.PanelBackColour2 : led.PanelBackColour1, mi.FadeValue));
                    }
                }
                else
                {
                    int intervalSpeed = 250;

                    if (led.ConditionResult != led.OldConditionResult)
                    {
                        if (led.ConditionResult && led.Blink)
                        {
                            led.BlinkCount = 0;
                            led.PulsateUp = true;
                            intervalSpeed = 100;
                            led.ColorFade = 0;
                        }
                    }

                    if (led.Blink || led.Pulsate)
                    {
                        if (led.Blink)
                        {
                            if (led.ColorFade == 1)
                            {
                                if (led.BlinkCount < 2)
                                {
                                    led.PulsateUp = false;
                                    led.BlinkCount++;
                                    intervalSpeed = 100;
                                }
                                else
                                {
                                    led.ColorFade = 0;
                                    led.PulsateUp = false;
                                }
                            }
                            if (led.ColorFade == 0 && led.ConditionResult && led.BlinkCount < 2)
                            {
                                led.PulsateUp = true;
                            }
                        }
                        else
                        {
                            if (led.ColorFade == 1)
                                led.PulsateUp = false;
                            if (led.ColorFade == 0 && led.ConditionResult)
                                led.PulsateUp = true;
                        }
                    }
                    else
                    {
                        if (led.ConditionResult)
                        {
                            led.PulsateUp = true;
                        }
                        else
                        {
                            led.PulsateUp = false;
                        }
                    }

                    int updateInterval = m.QuickestUpdateInterval(m.MOX);
                    updateInterval = Math.Min(updateInterval, intervalSpeed);
                    int steps_needed = (int)Math.Ceiling(intervalSpeed / (float)updateInterval);
                    float stepSize = 1 / (float)steps_needed;

                    System.Drawing.Color c;
                    if (led.PulsateUp)
                    {
                        led.ColorFade += stepSize;
                        c = ColorInterpolator.InterpolateBetween(led.FalseColour, led.TrueColour, led.ColorFade);
                    }
                    else
                    {
                        led.ColorFade -= stepSize;
                        c = ColorInterpolator.InterpolateBetween(led.FalseColour, led.TrueColour, led.ColorFade);
                    }

                    if ((led.ShowTrue && led.ConditionResult) || (led.ShowFalse && !led.ConditionResult))
                    { 
                        float xSize = targetWidth * led.SizeX;
                        float ySize = targetWidth * led.SizeY;
                        float posX = x + led.OffsetX * (targetWidth * m.XRatio);
                        float posY = y + led.OffsetY * (targetWidth * m.YRatio);
                        SharpDX.RectangleF igrect = new SharpDX.RectangleF(posX - (xSize / 2f), posY - (ySize / 2f), xSize, ySize);

                        _renderTarget.FillRectangle(igrect, getDXBrushForColour(c, 255));
                    }
                }
            }
            private bool renderTextOverlay(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m, bool render_text)
            {
                clsTextOverlay text_overlay = (clsTextOverlay)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                bool scrolling = false;

                bool do_cover_fade = (mi.FadeOnRx && !m.MOX) || (mi.FadeOnTx && m.MOX);
                if (!do_cover_fade && (text_overlay.PanelBackColour1 != text_overlay.PanelBackColour2))
                {
                    if (m.MOX != mi.MOXFadeToggle)
                    {
                        mi.FadeValue = 48;
                        mi.MOXFadeToggle = m.MOX;
                    }
                    else
                    {
                        int updateInterval = m.QuickestUpdateInterval(m.MOX);
                        updateInterval = Math.Min(updateInterval, 500);
                        // fade to take half a second
                        int steps_needed = (int)Math.Ceiling(500 / (float)updateInterval);
                        int stepSize = (int)Math.Ceiling(207 / (float)steps_needed); // 255-48 = 207

                        mi.FadeValue += stepSize;
                        if (mi.FadeValue > 255) mi.FadeValue = 255;
                    }
                }

                if (!render_text)
                {
                    if (text_overlay.ShowBackPanel)
                    {
                        SharpDX.RectangleF rectSC = new SharpDX.RectangleF(x, y, w, h);
                        _renderTarget.FillRectangle(rectSC, getDXBrushForColour(m.MOX ? text_overlay.PanelBackColour2 : text_overlay.PanelBackColour1, mi.FadeValue));
                    }
                }
                else
                {
                    // Determine the text to measure and display
                    string displayText = m.MOX ? text_overlay.ParsedText2 : text_overlay.ParsedText1;
                    string fontFamily = m.MOX ? text_overlay.FontFamily2 : text_overlay.FontFamily1;
                    float fontSize = m.MOX ? text_overlay.FontSize2 : text_overlay.FontSize1;
                    fontSize *= 2.1f; // when a container is added, and not resized, 72 point font needs this fudge to get it to be 72 point
                    FontStyle fontStyle = m.MOX ? text_overlay.Style2 : text_overlay.Style1;

                    // Calculate the text position
                    float xOffset = m.MOX ? text_overlay.TextXOffset2 : text_overlay.TextXOffset1;
                    float yOffset = m.MOX ? text_overlay.TextYOffset2 : text_overlay.TextYOffset1;
                    float textX = x + xOffset * (targetWidth * m.XRatio);
                    float textY = y + yOffset * (targetWidth * m.YRatio);

                    float scroll_x = m.MOX ? text_overlay.ScrollXOffset2 : text_overlay.ScrollXOffset1;
                    textX += scroll_x * (targetWidth * m.XRatio);

                    // scroll?
                    if (text_overlay.ScrollX != 0)
                    {
                        float fontSizeEmScaled = (fontSize / 16f) * (rect.Width / 52f);
                        SizeF szTextSize = measureString(displayText, fontFamily, fontStyle, fontSizeEmScaled, true);

                        int scroll_count = m.MOX ? text_overlay.ScrollXCount2 : text_overlay.ScrollXCount1;

                        if (szTextSize.Width > (targetWidth * m.XRatio) && scroll_count <= 1) // cant use w as it becomes 0 when panel hidden
                        {
                            scrolling = true;
                            scroll_x += (float)(text_overlay.ScrollX * deltaTimeMS / 1000f);
                            if (m.MOX)
                                text_overlay.ScrollXOffset2 = scroll_x;
                            else
                                text_overlay.ScrollXOffset1 = scroll_x;

                            if (textX < -szTextSize.Width)
                            {
                                float new_x = targetWidth - (xOffset * (targetWidth * m.XRatio));
                                scroll_x = new_x / (targetWidth * m.XRatio);
                                if (m.MOX)
                                {
                                    text_overlay.ScrollXOffset2 = scroll_x;
                                    text_overlay.ScrollXCount2++;
                                }
                                else
                                {
                                    text_overlay.ScrollXOffset1 = scroll_x;
                                    text_overlay.ScrollXCount1++;
                                }
                            }
                            else if (textX <= 0 && scroll_count == 1)
                            {
                                if (m.MOX)
                                {
                                    text_overlay.ScrollXOffset2 = 0;
                                    text_overlay.ScrollXCount2++;
                                    textX = 0;
                                }
                                else
                                {
                                    text_overlay.ScrollXOffset1 = 0;
                                    text_overlay.ScrollXCount1++;
                                    textX = 0;
                                }
                            }
                        }
                        else
                        {
                            if (m.MOX)
                                text_overlay.ScrollXOffset2 = 0;
                            else
                                text_overlay.ScrollXOffset1 = 0;
                        }
                    }

                    // Render the text
                    bool fill_background = (text_overlay.ShowTextBackColour1 && !m.MOX) || (text_overlay.ShowTextBackColour2 && m.MOX);
                    System.Drawing.Color fill_colour = m.MOX ? text_overlay.TextBackColour2 : text_overlay.TextBackColour1;
                    (float tw, float th) = plotText(displayText, textX, textY, h, rect.Width, fontSize, m.MOX ? text_overlay.TextColour2 : text_overlay.TextColour1, 255, fontFamily, fontStyle, false, false, 0, true, 0, 0, fill_background, fill_colour);

                    float rect_x = textX - (tw / 2);
                    float rect_y = textY - (th / 2);
                    System.Drawing.RectangleF bounds_rect = new System.Drawing.RectangleF(Math.Max(0, rect_x), Math.Max(0, rect_y), Math.Min(rect.Width, tw), Math.Min(rect.Height, th));
                    bool mouse_over_text = bounds_rect.Contains(text_overlay.MouseMovePoint);

                    if (mouse_over_text || text_overlay.MouseEntered)
                    {
                        float mx = -1;
                        float my = -1;
                        if (!mouse_over_text && w != 0 && h != 0)
                        {
                            mx = (text_overlay.MouseMovePoint.X - x) / w;
                            my = (text_overlay.MouseMovePoint.Y - y) / h;
                        }

                        if (mouse_over_text || (mx >= 0 && mx <= 1 && my >= 0 && my <= 1))
                        {
                            if (m.MOX)
                            {
                                text_overlay.ScrollXCount2 = 0;
                            }
                            else
                            {
                                text_overlay.ScrollXCount1 = 0;
                            }
                        }
                    }
                }
                return scrolling;
            }
            private void renderHistory(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsHistoryItem his = (clsHistoryItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                bool do_cover_fade = (mi.FadeOnRx && !m.MOX) || (mi.FadeOnTx && m.MOX);
                if (!do_cover_fade)
                {
                    if (m.MOX != mi.MOXFadeToggle)
                    {
                        mi.FadeValue = 48;
                        mi.MOXFadeToggle = m.MOX;
                    }
                    else
                    {
                        int updateInterval = m.QuickestUpdateInterval(m.MOX);
                        updateInterval = Math.Min(updateInterval, 500);
                        // fade to take half a second
                        int steps_needed = (int)Math.Ceiling(500 / (float)updateInterval);
                        int stepSize = (int)Math.Ceiling(207 / (float)steps_needed); // 255-48 = 207

                        mi.FadeValue += stepSize;
                        if (mi.FadeValue > 255) mi.FadeValue = 255;
                    }
                }

                SharpDX.RectangleF rectHis = new SharpDX.RectangleF(x, y, w, h);

                _renderTarget.FillRectangle(rectHis, getDXBrushForColour(his.BackColour));

                float spacer = w * 0.075f;
                float half_spacer = spacer / 2f;
                float quarter_spacer = spacer / 4f;
                float axis_line_width = Math.Max(1f, w * 0.002f);
                float data_line_width = Math.Max(1f, w * 0.002f);

                //x axis
                if(his.ShowScale1)
                    _renderTarget.DrawLine(new RawVector2(x + half_spacer + quarter_spacer, y + h - spacer), new RawVector2(x + w - half_spacer - quarter_spacer, y + h - spacer), getDXBrushForColour(his.LinesColour), axis_line_width);
                else
                    _renderTarget.DrawLine(new RawVector2(x + half_spacer + quarter_spacer, y + h - spacer), new RawVector2(x + w - quarter_spacer, y + h - spacer), getDXBrushForColour(his.LinesColour), axis_line_width);

                //left y axis
                _renderTarget.DrawLine(new RawVector2(x + spacer, y + quarter_spacer), new RawVector2(x + spacer, y + h - half_spacer - quarter_spacer), getDXBrushForColour(his.LinesColour), axis_line_width);

                //right y axis
                if(his.ShowScale1)
                    _renderTarget.DrawLine(new RawVector2(x + w - spacer, y + quarter_spacer), new RawVector2(x + w - spacer, y + h - half_spacer - quarter_spacer), getDXBrushForColour(his.LinesColour), axis_line_width);
                else
                    _renderTarget.DrawLine(new RawVector2(x + w - half_spacer, y + quarter_spacer), new RawVector2(x + w - half_spacer, y + h - half_spacer - quarter_spacer), getDXBrushForColour(his.LinesColour), axis_line_width);

                int pixel_width;
                if(his.ShowScale1)
                    pixel_width = (int)(w - spacer * 2f);
                else
                    pixel_width = (int)(w - spacer - half_spacer);

                //render axis 0 data
                float text_height = (w * 0.05f);
                float pix_space_vertical = (y + h - spacer) - (y + quarter_spacer);
                int text_labels = (int)(pix_space_vertical / text_height) + 2; // +2 a couple extra
                text_labels = Math.Max(2, text_labels);
                float text_y_step = pix_space_vertical / (float)(text_labels - 1);
                float start_x = x + spacer;
                float base_y = y + h - spacer;
                float last_x;
                SharpDX.RectangleF clip_rect;
                if(his.ShowScale1)
                    clip_rect = new SharpDX.RectangleF(x + spacer, y + quarter_spacer, w - spacer * 2f, h - quarter_spacer - spacer);
                else
                    clip_rect = new SharpDX.RectangleF(x + spacer, y + quarter_spacer, w - spacer - quarter_spacer, h - quarter_spacer - spacer);

                lock (his.DataLock1)
                {
                    int total1 = his.History1.Count;
                    if (total1 > 1 && his.ShowScale1)
                    {
                        last_x = start_x;
                        float range1 = his.Range1;
                        range1 = Math.Max(1, range1);
                        float number_step1 = range1 / (float)(text_labels - 1);
                        float min1 = his.Min1;
                        for (int i = 0; i < text_labels; i++)
                        {
                            float t_y = y + h - spacer - (i * text_y_step) - (text_height / 4f);

                            if (his.ShowScale1)
                            {
                                //right numbers
                                float val = min1 + (i * number_step1);
                                plotText(val.ToString("f1"), x + w - spacer + quarter_spacer, t_y, h, rect.Width, 11f, his.Axis1Colour, 255, "Trebuchet MS", FontStyle.Regular, false, false, spacer);
                            }

                            //ticks/grid line
                            if (i > 0)
                            {
                                t_y += (text_height / 4f);
                                // right tick
                                _renderTarget.DrawLine(new RawVector2(x + w - spacer, t_y), new RawVector2(x + w - half_spacer - quarter_spacer, t_y), getDXBrushForColour(his.LinesColour, 96), data_line_width);
                            }
                        }

                        float y_scale1 = (h - spacer - quarter_spacer) / range1;
                        float last_y1 = (his.History1[0].value - min1) * y_scale1;

                        _renderTarget.PushAxisAlignedClip(clip_rect, AntialiasMode.Aliased);
                        float pix_spacing = pixel_width / ((float)total1 - 1);
                        List<clsHistoryItem.HistoryData> time_tags = new List<clsHistoryItem.HistoryData>();
                        for (int n = 1; n < total1; n++)
                        {
                            float end_x = start_x + (n * pix_spacing);

                            clsHistoryItem.HistoryData hd1 = his.History1[n];
                            float end_y1 = (hd1.value - min1) * y_scale1;
                            _renderTarget.DrawLine(
                            new RawVector2(last_x, base_y - last_y1),
                            new RawVector2(end_x, base_y - end_y1),
                            getDXBrushForColour(his.Axis1Colour), data_line_width);
                            last_y1 = end_y1;
                            last_x = end_x;
                        }
                        _renderTarget.PopAxisAlignedClip();
                    }
                }
                lock (his.DataLock0)
                {
                    int total0 = his.History0.Count;
                    if (total0 > 1)
                    {
                        last_x = start_x;
                        float range0 = his.Range0;
                        range0 = Math.Max(1, range0);

                        //y-axis numbers + lines
                        float min0 = his.Min0;
                        float number_step0 = range0 / (float)(text_labels - 1);
                        for (int i = 0; i < text_labels; i++)
                        {
                            float t_y = y + h - spacer - (i * text_y_step) - (text_height / 4f);

                            //left numbers
                            float val = min0 + (i * number_step0);
                            plotText(val.ToString("f1"), x + spacer - quarter_spacer, t_y, h, rect.Width, 11f, his.Axis0Colour, 255, "Trebuchet MS", FontStyle.Regular, true, false, spacer);

                            //ticks/grid line
                            if (i > 0)
                            {
                                t_y += (text_height / 4f);
                                // full grid line
                                _renderTarget.DrawLine(new RawVector2(x + spacer - quarter_spacer, t_y), new RawVector2(x + w - (his.ShowScale1 ? spacer : quarter_spacer), t_y), getDXBrushForColour(his.LinesColour, 96), data_line_width);
                            }
                        }

                        float y_scale0 = (h - spacer - quarter_spacer) / range0;                            
                        float last_y0 = (his.History0[0].value - min0) * y_scale0;                                                       

                        _renderTarget.PushAxisAlignedClip(clip_rect, AntialiasMode.Aliased);
                        float pix_spacing = pixel_width / ((float)total0 - 1);
                        List<clsHistoryItem.HistoryData> time_tags = new List<clsHistoryItem.HistoryData>();
                        for (int n = 1; n < total0; n++)
                        {
                            clsHistoryItem.HistoryData hd0 = his.History0[n];

                            float end_x = start_x + (n * pix_spacing);
                            float end_y0 = (hd0.value - min0) * y_scale0;

                            _renderTarget.DrawLine(
                                new RawVector2(last_x, base_y - last_y0),
                                new RawVector2(end_x, base_y - end_y0),
                                getDXBrushForColour(his.Axis0Colour), data_line_width);
                            last_y0 = end_y0;

                            if (hd0.show_time)
                            {
                                hd0.index = n;
                                time_tags.Add(hd0);
                            }

                            last_x = end_x;
                        }
                        _renderTarget.PopAxisAlignedClip();

                        // time tags
                        foreach (clsHistoryItem.HistoryData hd in time_tags)
                        {
                            float s_x;
                            if(his.ShowScale1)
                                s_x = start_x + (hd.index / (float)(total0 - 1)) * (w - spacer * 2f);
                            else
                                s_x = start_x + (hd.index / (float)(total0 - 1)) * (w - spacer - half_spacer);
                            float s_y = y + h - half_spacer;
                            _renderTarget.DrawLine(new RawVector2(s_x, s_y - half_spacer), new RawVector2(s_x, s_y - half_spacer + quarter_spacer), getDXBrushForColour(his.LinesColour, 96), data_line_width);
                            plotText(hd.time.ToString("HH:mm:ss"), s_x, s_y, h, rect.Width, 10f, his.TimeColour, 255, "Trebuchet MS", FontStyle.Regular, false, false, 0, true, 0, 45);
                        }

                        //time axis
                        float pix_space_horiz = (x + w - spacer) - (x + spacer);
                        float text_height_time = (w * 0.05f);
                        int can_fit = (int)(pix_space_horiz / text_height_time);
                        can_fit = Math.Max(2, can_fit);
                        his.MaxTimeLables = can_fit;

                        if (his.ActiveTimeLabels < his.MaxTimeLables)
                        {
                            int labels_left = his.MaxTimeLables - his.ActiveTimeLabels;

                            if (labels_left > 0)
                            {
                                // we can add one                        
                                int interval_seconds = his.KeepFor / labels_left;
                                interval_seconds = Math.Max(5, interval_seconds);

                                if (DateTime.UtcNow >= his.LastTimeLabelAddTime.AddSeconds(interval_seconds))
                                {
                                    // time for another
                                    his.NextAddMakeTimeLabel = true;
                                }
                            }
                        }

                        //plotText(his.IndexTracker.ToString(), x , y, h, rect.Width, 12f, System.Drawing.Color.Red, 255, "Trebuchet MS", FontStyle.Regular);
                        //plotText(sampleRate.ToString(), x, y + 30, h, rect.Width, 12f, System.Drawing.Color.Red, 255, "Trebuchet MS", FontStyle.Regular);
                    }
                }               
            }
            private void renderSpacer(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsSpacerItem spacer = (clsSpacerItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                bool do_cover_fade = (mi.FadeOnRx && !m.MOX) || (mi.FadeOnTx && m.MOX);
                if (!do_cover_fade && (spacer.Colour1 != spacer.Colour2))
                {
                    if (m.MOX != mi.MOXFadeToggle)
                    {
                        mi.FadeValue = 48;
                        mi.MOXFadeToggle = m.MOX;
                    }
                    else
                    {
                        int updateInterval = m.QuickestUpdateInterval(m.MOX);
                        updateInterval = Math.Min(updateInterval, 500);
                        // fade to take half a second
                        int steps_needed = (int)Math.Ceiling(500 / (float)updateInterval);
                        int stepSize = (int)Math.Ceiling(207 / (float)steps_needed); // 255-48 = 207

                        mi.FadeValue += stepSize;
                        if (mi.FadeValue > 255) mi.FadeValue = 255;
                    }
                }

                SharpDX.RectangleF rectSC = new SharpDX.RectangleF(x, y, w, h);

                _renderTarget.FillRectangle(rectSC, getDXBrushForColour(m.MOX ? spacer.Colour2 : spacer.Colour1, mi.FadeValue));
            }
            private void renderWebImage(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsWebImage webimg = (clsWebImage)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                bool do_cover_fade = (mi.FadeOnRx && !m.MOX) || (mi.FadeOnTx && m.MOX);
                if (!do_cover_fade)
                {
                    if (m.MOX != mi.MOXFadeToggle)
                    {
                        mi.FadeValue = 48;
                        mi.MOXFadeToggle = m.MOX;
                    }
                    else
                    {
                        int updateInterval = m.QuickestUpdateInterval(m.MOX);
                        updateInterval = Math.Min(updateInterval, 500);
                        // fade to take half a second
                        int steps_needed = (int)Math.Ceiling(500 / (float)updateInterval);
                        int stepSize = (int)Math.Ceiling(207 / (float)steps_needed); // 255-48 = 207

                        mi.FadeValue += stepSize;
                        if (mi.FadeValue > 255) mi.FadeValue = 255;
                    }
                }

                SharpDX.RectangleF rectSC = new SharpDX.RectangleF(x, y, w, h);

                if(webimg.Bitmap != null)
                {
                    string key = "webimg_" + webimg.FetcherGuid.ToString();

                    if (!_images.ContainsKey(key))
                    {
                        // convert + add
                        SharpDX.Direct2D1.Bitmap img = bitmapFromSystemBitmap(_renderTarget, webimg.Bitmap, key);
                        if (img != null)
                        {
                            img.Tag = webimg.BitmapGuid; // guid for web image, we also use this as a bool for skin image
                            _images.Add(key, img);
                        }
                    }
                    
                    if(_images.ContainsKey(key))
                    {
                        // has image changed from the one we put in _images
                        SharpDX.Direct2D1.Bitmap b = _images[key];
                        if((Guid)b.Tag != webimg.BitmapGuid) // the tag is used to ID the web image, if it is different to the one cached, regenerate the image
                        {
                            // new image, need to remove _image, and the stream cache, and re-add
                            _images[key].Dispose();
                            _images.Remove(key);

                            //remove from stream cache
                            RemoveStreamData(key);

                            // convert + add
                            SharpDX.Direct2D1.Bitmap img = bitmapFromSystemBitmap(_renderTarget, webimg.Bitmap, key);
                            if (img != null)
                            {
                                img.Tag = webimg.BitmapGuid; // guid for web image, we also use this as a bool for skin image
                                _images.Add(key, img);
                            }
                        }
                    }

                    if (_images.ContainsKey(key))
                    {
                        SharpDX.RectangleF imgRect = new SharpDX.RectangleF(x, y, w, h);

                        SharpDX.Direct2D1.Bitmap b = _images[key];

                        // maintain aspect ratio, the clip removes anything outside the rect
                        float im_w = b.Size.Width;
                        float im_h = b.Size.Height;

                        if (w > h)
                            imgRect.Height = imgRect.Width * (im_h / im_w);
                        else
                            imgRect.Width = imgRect.Height * (im_w / im_h);

                        _renderTarget.DrawBitmap(b, imgRect, 1f, BitmapInterpolationMode.Linear);
                    }
                }
                else
                {
                    plotText("No Image", x + w/2, y + h/2, h, rect.Width, 36f, System.Drawing.Color.DarkGray, 255, "Trebuchet MS", FontStyle.Regular, false, true);
                }
            }
            private string convertDegreesToCardinal(float degrees)
            {
                string[] cardinals = new string[]
                {
                    "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
                    "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"
                };

                float[] boundaries = new float[]
                {
                    0f, 11.25f, 33.75f, 56.25f, 78.75f, 101.25f, 123.75f, 146.25f,
                    168.75f, 191.25f, 213.75f, 236.25f, 258.75f, 281.25f, 303.75f, 326.25f, 348.75f
                };

                degrees = degrees % 360;

                for (int i = 0; i < boundaries.Length - 1; i++)
                {
                    if (degrees >= boundaries[i] && degrees < boundaries[i + 1])
                    {
                        return cardinals[i];
                    }
                }

                return cardinals[0];
            }
            private bool _showing_rotator_ele_drag = false;
            private bool _rotator_was_dragging = false;
            private float _dragging_rotator_degrees = -1;
            private bool _dragging_rotator_ele = false;
            private int _dragging_old_update_rate = -1;
            private float _rotator_az_angle_deg = -999;
            private float _rotator_ele_angle_deg = -999;
            private void renderRotator(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsRotatorItem rotator = (clsRotatorItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                //SharpDX.RectangleF mirect = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.DrawRectangle(mirect, getDXBrushForColour(System.Drawing.Color.Green));

                SharpDX.Direct2D1.Brush line_br = getDXBrushForColour(rotator.ArrowColour, 255);
                SharpDX.Direct2D1.Brush big_dot_br = getDXBrushForColour(rotator.BigBlobColour, 255);
                SharpDX.Direct2D1.Brush small_dot_br = getDXBrushForColour(rotator.SmallBlobColour, 255);
                SharpDX.Direct2D1.Brush beam_widh_br = getDXBrushForColour(rotator.BeamWidthColour, (int)(255 * rotator.BeamWidthAlpha));

                float xShift = rotator.ViewMode == clsRotatorItem.RotatorMode.BOTH ? 2f * (w * 0.0125f) : 0;
                bool send_stop = false;
                float radius_stop_circle = rotator.ViewMode == clsRotatorItem.RotatorMode.ELE ? (h * 0.09f) / 2f : (h * 0.15f) / 2f; // to include the numbers when clicking to move the rotator

                if (rotator.Primary)
                {
                    if (rotator.ViewMode == clsRotatorItem.RotatorMode.ELE) return; // az is not drawn

                    Vector2 centre = new Vector2(xShift + x + h / 2f, y + h / 2f);
                    Vector2 pos = new Vector2(0, 0);
                    Vector2 pointer_tip = new Vector2(0, 0);
                    Ellipse elipse = new Ellipse(pos, h * 0.01f, h * 0.01f);

                    float radius = (h * 0.8f) / 2f;
                    float radius_text = (h * 0.92f) / 2f;
                    float radius_inner_arrow = (h * 0.75f) / 2f;
                    float radius_tip_arrow = (h * 0.78f) / 2f;
                    float radius_tip_arrow_extra = (h * 0.98f) / 2f; // to include the numbers when clicking to move the rotator
                    float cx;
                    float cy;
                    float rad;
                    float text_scale = rotator.ViewMode == clsRotatorItem.RotatorMode.AZ ? 1.5f : 1f;
                    text_scale *= rotator.ViewMode == clsRotatorItem.RotatorMode.BOTH ? 1f : rotator.Padding;

                    float degrees_az = Math.Abs(rotator.Value) % 360f;
                    bool cardinals = rotator.ShowCardinals;
                    bool show_beam_width = rotator.ShowBeamWidth;
                    float beam_width = rotator.BeamWidth;
                    if (cardinals)
                    {
                        // small dots
                        for (int deg = 0; deg <= 350; deg += 10)
                        {
                            rad = (deg - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                            cx = centre.X + radius * (float)Math.Cos(rad);
                            cy = centre.Y + radius * (float)Math.Sin(rad);
                            pos.X = cx; pos.Y = cy;
                            elipse.Point.X = pos.X; elipse.Point.Y = pos.Y;
                            if (deg % 45 != 0)
                            {
                                elipse.RadiusX = h * 0.005f; elipse.RadiusY = h * 0.005f;
                                _renderTarget.FillEllipse(elipse, small_dot_br);
                            }
                        }
                        // big dots and text
                        for (int deg = 0; deg <= 315; deg += 45)
                        {
                            rad = (deg - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                            cx = centre.X + radius * (float)Math.Cos(rad);
                            cy = centre.Y + radius * (float)Math.Sin(rad);
                            pos.X = cx; pos.Y = cy;
                            elipse.Point.X = pos.X; elipse.Point.Y = pos.Y;
                            if (deg % 45 == 0)
                            {
                                elipse.RadiusX = h * 0.015f; elipse.RadiusY = h * 0.015f;
                                _renderTarget.FillEllipse(elipse, big_dot_br);

                                string card = "";
                                switch (deg)
                                {
                                    case 0:
                                        card = "N";
                                        break;
                                    case 45:
                                        card = "NE";
                                        break;
                                    case 90:
                                        card = "E";
                                        break;
                                    case 135:
                                        card = "SE";
                                        break;
                                    case 180:
                                        card = "S";
                                        break;
                                    case 225:
                                        card = "SW";
                                        break;
                                    case 270:
                                        card = "W";
                                        break;
                                    case 315:
                                        card = "NW";
                                        break;
                                }

                                cx = centre.X + radius_text * (float)Math.Cos(rad);
                                cy = centre.Y + radius_text * (float)Math.Sin(rad);
                                plotText(card, cx, cy, h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, true, 0, true);
                            }
                        }

                    }
                    else
                    {
                        for (int deg = 0; deg <= 350; deg += 10)
                        {
                            rad = (deg - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                            cx = centre.X + radius * (float)Math.Cos(rad);
                            cy = centre.Y + radius * (float)Math.Sin(rad);
                            pos.X = cx; pos.Y = cy;
                            elipse.Point.X = pos.X; elipse.Point.Y = pos.Y;
                            if (deg % 30 == 0)
                            {
                                elipse.RadiusX = h * 0.015f; elipse.RadiusY = h * 0.015f;
                                _renderTarget.FillEllipse(elipse, big_dot_br);

                                cx = centre.X + radius_text * (float)Math.Cos(rad);
                                cy = centre.Y + radius_text * (float)Math.Sin(rad);
                                plotText(deg.ToString(), cx, cy, h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, true, 0, true);
                            }
                            else
                            {
                                elipse.RadiusX = h * 0.005f; elipse.RadiusY = h * 0.005f;
                                _renderTarget.FillEllipse(elipse, small_dot_br);
                            }
                        }
                    }

                    // beam width
                    if (show_beam_width)
                    {
                        Vector2 arc_edge_1 = new Vector2(0, 0);
                        Vector2 arc_edge_2 = new Vector2(0, 0);
                        beam_width /= 2f;
                        rad = (degrees_az - 90 - beam_width) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                        cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                        cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);
                        arc_edge_1.X = cx; arc_edge_1.Y = cy;
                        rad = (degrees_az - 90 + beam_width) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                        cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                        cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);
                        arc_edge_2.X = cx; arc_edge_2.Y = cy;

                        PathGeometry sharpGeometry = new PathGeometry(_renderTarget.Factory);

                        GeometrySink geo = sharpGeometry.Open();
                        geo.BeginFigure(new SharpDX.Vector2(centre.X, centre.Y), FigureBegin.Filled);

                        geo.AddLine(new SharpDX.Vector2(arc_edge_1.X, arc_edge_1.Y));

                        ArcSegment arcSegment = new ArcSegment();
                        arcSegment.Point = new SharpDX.Vector2(arc_edge_2.X, arc_edge_2.Y);
                        arcSegment.SweepDirection = SweepDirection.Clockwise;
                        arcSegment.ArcSize = beam_width <= 90f ? ArcSize.Small : ArcSize.Large;
                        arcSegment.Size = new Size2F(radius_tip_arrow, radius_tip_arrow);
                        geo.AddArc(arcSegment);

                        geo.EndFigure(FigureEnd.Closed); // adds the closing line
                        geo.Close();

                        _renderTarget.FillGeometry(sharpGeometry, beam_widh_br);
                    }

                    // arrow tip
                    rad = (degrees_az - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                    cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                    cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);
                    pointer_tip.X = cx; pointer_tip.Y = cy;
                    _renderTarget.DrawLine(centre, pointer_tip, line_br, h * 0.01f);

                    // arrow side, offset 3 degrees, and inset
                    rad = (degrees_az - 90 - 3) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                    cx = centre.X + radius_inner_arrow * (float)Math.Cos(rad);
                    cy = centre.Y + radius_inner_arrow * (float)Math.Sin(rad);
                    pos.X = cx; pos.Y = cy;
                    _renderTarget.DrawLine(pointer_tip, pos, line_br, h * 0.01f);

                    rad = (degrees_az - 90 + 3) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                    cx = centre.X + radius_inner_arrow * (float)Math.Cos(rad);
                    cy = centre.Y + radius_inner_arrow * (float)Math.Sin(rad);
                    pos.X = cx; pos.Y = cy;
                    _renderTarget.DrawLine(pointer_tip, pos, line_br, h * 0.01f);
                    //

                    // az text
                    if (rotator.ViewMode == clsRotatorItem.RotatorMode.BOTH)
                    {
                        cx = x + w * 0.75f;
                        cy = y + h * 0.575f;
                        plotText(convertDegreesToCardinal(degrees_az), cx, cy, h, rect.Width, rotator.FontSize * 2f * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, false, 0, true);
                        plotText(" cardinal", cx - (w * 0.01f), cy + (h * 0.035f), h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, true, false, 0, true);
                        cy = y + h * 0.7f;
                        plotText(degrees_az.ToString("f1") + "°", cx, cy, h, rect.Width, rotator.FontSize * 2f * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, false, 0, true);
                        plotText("  azimuth", cx - (w * 0.01f), cy + (h * 0.035f), h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, true, false, 0, true);
                    }
                    else
                    {
                        cx = x + w * 0.5f;
                        cy = y + h * 0.525f;
                        plotText(convertDegreesToCardinal(degrees_az), cx, cy, h, rect.Width, rotator.FontSize * 2f * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, false, 0, true);
                        plotText(" cardinal", cx - (w * 0.01f), cy + (h * 0.035f), h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, true, false, 0, true);
                        cy = y + h * 0.65f;
                        plotText(degrees_az.ToString("f1") + "°", cx, cy, h, rect.Width, rotator.FontSize * 2f * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, false, 0, true);
                        plotText("  azimuth", cx - (w * 0.01f), cy + (h * 0.035f), h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, true, false, 0, true);
                    }

                    // check if arrow at desination
                    if (degrees_az >= _rotator_az_angle_deg - 3 && degrees_az <= _rotator_az_angle_deg + 3)
                        _rotator_az_angle_deg = -999;

                    if (rotator.AllowControl)
                    {
                        bool stop_drawn = false;
                        if (rotator.MouseEntered)
                        {
                            // draw red circle at centre for stop command                            
                            cx = centre.X + radius_stop_circle * (float)Math.Cos(rad);
                            cy = centre.Y + radius_stop_circle * (float)Math.Sin(rad);
                            float dist = calculateDistance(new PointF(centre.X, centre.Y), new PointF(cx, cy));
                            float distToMouse = calculateDistance(new PointF(centre.X, centre.Y), rotator.MouseMovePoint);
                            if(distToMouse <= dist)
                            {
                                elipse.Point.X = centre.X;
                                elipse.Point.Y = centre.Y;
                                elipse.RadiusX = radius_stop_circle; elipse.RadiusY = radius_stop_circle;
                                _renderTarget.FillEllipse(elipse, getDXBrushForColour(System.Drawing.Color.Red));
                                stop_drawn = true;

                                if (rotator.MouseButtonDown)
                                {
                                    send_stop = true;
                                    _rotator_az_angle_deg = -999;
                                }
                            }

                            if (!stop_drawn && rotator.MouseButtonDown)
                            {
                                float temp_degrees = 0;
                                SharpDX.Direct2D1.Brush rotator_control_br = getDXBrushForColour(rotator.ControlColour, 255);

                                if (!_rotator_was_dragging && _dragging_old_update_rate == -1)
                                {
                                    _dragging_old_update_rate = rotator.UpdateInterval;
                                    rotator.UpdateInterval = 16;
                                    m.UpdateIntervals();
                                }

                                // find outer edge
                                rad = (temp_degrees - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                                cx = centre.X + radius_tip_arrow_extra * (float)Math.Cos(rad);
                                cy = centre.Y + radius_tip_arrow_extra * (float)Math.Sin(rad);
                                dist = calculateDistance(new PointF(centre.X, centre.Y), new PointF(cx, cy));
                                distToMouse = calculateDistance(new PointF(centre.X, centre.Y), rotator.MouseDownPoint);

                                if (distToMouse <= dist)
                                {
                                    elipse.Point.X = centre.X;
                                    elipse.Point.Y = centre.Y;
                                    elipse.RadiusX = h * 0.015f; elipse.RadiusY = h * 0.015f;
                                    _renderTarget.FillEllipse(elipse, big_dot_br);

                                    //get the angle through the mouse using atan2, radians
                                    float deltaX = rotator.MouseMovePoint.X - centre.X;
                                    float deltaY = rotator.MouseMovePoint.Y - centre.Y;
                                    rad = (float)Math.Atan2(deltaY, deltaX);
                                    cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                                    cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);

                                    _renderTarget.DrawLine(centre, new Vector2(cx, cy), rotator_control_br, h * 0.01f);

                                    temp_degrees = rad * (180.0f / (float)Math.PI); // the angle we need to send
                                    temp_degrees = (temp_degrees + 90) % 360;
                                    if (temp_degrees < 0)
                                    {
                                        temp_degrees += 360;
                                    }
                                    _dragging_rotator_degrees = temp_degrees;
                                    _dragging_rotator_ele = false;
                                    _rotator_was_dragging = true;
                                }
                            }
                        }

                        if (_rotator_az_angle_deg != -999)
                        {
                            SharpDX.Direct2D1.Brush rotator_control_br = getDXBrushForColour(rotator.ControlColour, 255);
                            //draw to angle
                            //set to -999 when pointer gets close

                            rad = (_rotator_az_angle_deg - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                            cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                            cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);

                            _renderTarget.DrawLine(centre, new Vector2(cx, cy), rotator_control_br, h * 0.01f);

                            if (stop_drawn) //draw over the top again
                            {
                                elipse.Point.X = centre.X;
                                elipse.Point.Y = centre.Y;
                                elipse.RadiusX = radius_stop_circle; elipse.RadiusY = radius_stop_circle;
                                _renderTarget.FillEllipse(elipse, getDXBrushForColour(System.Drawing.Color.Red));
                            }
                        }
                    }
                }
                else
                {
                    if (rotator.ViewMode == clsRotatorItem.RotatorMode.AZ) return; // ele is not drawn

                    Vector2 centre = new Vector2(0, 0);// = new Vector2(xShift + x + h / 2f, y + h / 2f);
                    Vector2 pos = new Vector2(0, 0);
                    Vector2 pointer_tip = new Vector2(0, 0);
                    Ellipse elipse = new Ellipse(pos, h * 0.01f, h * 0.01f);

                    float radius = (h * 0.8f) / 2f;
                    float radius_text = (h * 0.92f) / 2f;
                    float radius_inner_arrow = (h * 0.75f) / 2f;
                    float radius_tip_arrow = (h * 0.78f) / 2f;
                    float radius_tip_arrow_extra = (h * 0.98f) / 2f; // to include the numbers when clicking to move the rotator
                    float cx;
                    float cy;
                    float rad;
                    float text_scale = rotator.ViewMode == clsRotatorItem.RotatorMode.ELE ? 1.5f : 1f;
                    text_scale *= rotator.ViewMode == clsRotatorItem.RotatorMode.BOTH ? 1f : rotator.Padding;

                    if (rotator.ViewMode == clsRotatorItem.RotatorMode.ELE)
                    {
                        radius = h * 0.84f;
                        radius_text = (h * 0.90f);
                        radius_inner_arrow = (h * 0.75f);
                        radius_tip_arrow = (h * 0.78f);
                        radius_tip_arrow_extra = (h * 0.98f);
                        centre.X = x + (h / 2f) - (radius / 2f);
                        centre.Y = y + h - (4f * (w * 0.0125f));
                    }
                    else
                    {
                        centre.X = w - xShift - h / 2f;
                        centre.Y = y + h / 2f;
                    }

                    for (int deg = 0; deg <= 90; deg += 5)
                    {
                        rad = (deg - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                        cx = centre.X + radius * (float)Math.Cos(rad);
                        cy = centre.Y + radius * (float)Math.Sin(rad);
                        pos.X = cx; pos.Y = cy;
                        elipse.Point.X = pos.X; elipse.Point.Y = pos.Y;
                        if (deg % 15 == 0)
                        {
                            elipse.RadiusX = h * 0.015f; elipse.RadiusY = h * 0.015f;
                            _renderTarget.FillEllipse(elipse, big_dot_br);

                            cx = centre.X + radius_text * (float)Math.Cos(rad);
                            cy = centre.Y + radius_text * (float)Math.Sin(rad);
                            plotText((90 - deg).ToString(), cx, cy, h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, true, 0, false);
                        }
                        else
                        {
                            elipse.RadiusX = h * 0.005f; elipse.RadiusY = h * 0.005f;
                            _renderTarget.FillEllipse(elipse, small_dot_br);
                        }
                    }

                    float degrees_ele = Math.Abs(rotator.Value) % 90f;
                    // arrow tip
                    rad = (-degrees_ele) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                    cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                    cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);
                    pointer_tip.X = cx; pointer_tip.Y = cy;
                    _renderTarget.DrawLine(centre, pointer_tip, line_br, h * 0.01f);

                    // arrow side, offset 3 degrees, and inset
                    rad = (-degrees_ele - 3) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                    cx = centre.X + radius_inner_arrow * (float)Math.Cos(rad);
                    cy = centre.Y + radius_inner_arrow * (float)Math.Sin(rad);
                    pos.X = cx; pos.Y = cy;
                    _renderTarget.DrawLine(pointer_tip, pos, line_br, h * 0.01f);

                    rad = (-degrees_ele + 3) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                    cx = centre.X + radius_inner_arrow * (float)Math.Cos(rad);
                    cy = centre.Y + radius_inner_arrow * (float)Math.Sin(rad);
                    pos.X = cx; pos.Y = cy;
                    _renderTarget.DrawLine(pointer_tip, pos, line_br, h * 0.01f);
                    //

                    // ele text
                    if (rotator.ViewMode == clsRotatorItem.RotatorMode.ELE)
                    {
                        cx = x + w * 0.4f;
                        cy = y + h * 0.75f;
                        plotText(degrees_ele.ToString("f1") + "°", cx, cy, h, rect.Width, rotator.FontSize * 2f * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, false, 0, true);
                        plotText("elevation", cx - (w * 0.01f), cy + (h * 0.035f), h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, true, false, 0, true);
                    }
                    else
                    {
                        cx = x + w * 0.75f;
                        cy = y + h * 0.825f;
                        plotText(degrees_ele.ToString("f1") + "°", cx, cy, h, rect.Width, rotator.FontSize * 2f * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, false, false, 0, true);
                        plotText("elevation", cx - (w * 0.01f), cy + (h * 0.035f), h, rect.Width, rotator.FontSize * text_scale, rotator.OuterTextColour, 255, rotator.FontFamily, rotator.Style, true, false, 0, true);
                    }

                    // check if arrow at desination
                    if (degrees_ele >= _rotator_ele_angle_deg - 3 && degrees_ele <= _rotator_ele_angle_deg + 3)
                        _rotator_ele_angle_deg = -999;

                    if (rotator.AllowControl)
                    {
                        bool stop_drawn = false;
                        if (rotator.MouseEntered)
                        {
                            // draw red circle at pointer origin for stop command                            
                            cx = centre.X + radius_stop_circle * (float)Math.Cos(rad);
                            cy = centre.Y + radius_stop_circle * (float)Math.Sin(rad);
                            float dist = calculateDistance(new PointF(centre.X, centre.Y), new PointF(cx, cy));
                            float distToMouse = calculateDistance(new PointF(centre.X, centre.Y), rotator.MouseMovePoint);
                            if (distToMouse <= dist)
                            {
                                elipse.Point.X = centre.X;
                                elipse.Point.Y = centre.Y;
                                elipse.RadiusX = radius_stop_circle; elipse.RadiusY = radius_stop_circle;
                                _renderTarget.FillEllipse(elipse, getDXBrushForColour(System.Drawing.Color.Red));
                                stop_drawn = true;

                                if (rotator.MouseButtonDown)
                                {
                                    send_stop = true;
                                    _rotator_ele_angle_deg = -999;
                                }
                            }

                            if (!stop_drawn && rotator.MouseButtonDown)
                            {
                                float temp_degrees = 0;
                                SharpDX.Direct2D1.Brush rotator_control_br = getDXBrushForColour(rotator.ControlColour, 255);
                                if (!_rotator_was_dragging && _dragging_old_update_rate == -1)
                                {
                                    _dragging_old_update_rate = rotator.UpdateInterval;
                                    rotator.UpdateInterval = 16;
                                    m.UpdateIntervals();
                                }
                                //find outer edge
                                temp_degrees = 0;
                                rad = (temp_degrees - 90) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                                cx = centre.X + radius_tip_arrow_extra * (float)Math.Cos(rad);
                                cy = centre.Y + radius_tip_arrow_extra * (float)Math.Sin(rad);
                                dist = calculateDistance(new PointF(centre.X, centre.Y), new PointF(cx, cy));
                                distToMouse = calculateDistance(new PointF(centre.X, centre.Y), rotator.MouseDownPoint);

                                float deltaX = rotator.MouseMovePoint.X - centre.X;
                                float deltaY = rotator.MouseMovePoint.Y - centre.Y;
                                rad = (float)Math.Atan2(deltaY, deltaX);
                                temp_degrees = -rad * (180.0f / (float)Math.PI);

                                if (distToMouse <= dist && ((temp_degrees >= 0 && temp_degrees <= 90) || _showing_rotator_ele_drag))
                                {
                                    //get the angle through the mouse using atan2, radians
                                    if (temp_degrees > 90) temp_degrees = 90;
                                    if (temp_degrees < 0) temp_degrees = 0;

                                    rad = rad = temp_degrees * -((float)Math.PI / 180.0f);
                                    cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                                    cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);

                                    elipse.Point.X = centre.X;
                                    elipse.Point.Y = centre.Y;
                                    elipse.RadiusX = h * 0.015f; elipse.RadiusY = h * 0.015f;
                                    _renderTarget.FillEllipse(elipse, big_dot_br);

                                    _renderTarget.DrawLine(centre, new Vector2(cx, cy), rotator_control_br, h * 0.01f);

                                    _showing_rotator_ele_drag = true;
                                    _dragging_rotator_degrees = temp_degrees;
                                    _dragging_rotator_ele = true;
                                    _rotator_was_dragging = true;
                                }
                                else
                                    _showing_rotator_ele_drag = false;
                            }
                            else if (_showing_rotator_ele_drag) _showing_rotator_ele_drag = false;
                        }
                        else if (_showing_rotator_ele_drag) _showing_rotator_ele_drag = false;

                        if (_rotator_ele_angle_deg != -999)
                        {
                            SharpDX.Direct2D1.Brush rotator_control_br = getDXBrushForColour(rotator.ControlColour, 255);
                            //draw to angle
                            //set to -999 when pointer gets close

                            rad = (-_rotator_ele_angle_deg) * (float)Math.PI / 180.0f; // Convert degrees to radians, and -90 to top
                            cx = centre.X + radius_tip_arrow * (float)Math.Cos(rad);
                            cy = centre.Y + radius_tip_arrow * (float)Math.Sin(rad);

                            _renderTarget.DrawLine(centre, new Vector2(cx, cy), rotator_control_br, h * 0.01f);

                            if(stop_drawn) // draw over the top again
                            {
                                elipse.Point.X = centre.X;
                                elipse.Point.Y = centre.Y;
                                elipse.RadiusX = radius_stop_circle; elipse.RadiusY = radius_stop_circle;
                                _renderTarget.FillEllipse(elipse, getDXBrushForColour(System.Drawing.Color.Red));
                            }
                        }
                    }
                }

                //send rotator position message
                if (rotator.AllowControl && !rotator.MouseButtonDown && _rotator_was_dragging && _dragging_rotator_degrees >= 0)
                {
                    _rotator_was_dragging = false;

                    if (_dragging_rotator_ele)
                        _rotator_ele_angle_deg = _dragging_rotator_degrees;
                    else
                        _rotator_az_angle_deg = _dragging_rotator_degrees;

                    rotator.SendRotatorMessage(_dragging_rotator_ele, _dragging_rotator_degrees, send_stop);

                    _dragging_rotator_ele = false;
                    _dragging_rotator_degrees = -1;
                    rotator.UpdateInterval = _dragging_old_update_rate;
                    _dragging_old_update_rate = -1;
                    m.UpdateIntervals();
                }
                if(rotator.AllowControl && send_stop)
                {
                    rotator.SendRotatorMessage(false, -1, true);
                }
            }
            private float calculateDistance(PointF point1, PointF point2)
            {
                float deltaX = point2.X - point1.X;
                float deltaY = point2.Y - point1.Y;
                return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
            }
            private void renderEye(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsMagicEyeItem magicEye = (clsMagicEyeItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                //SharpDX.RectangleF mirect = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.DrawRectangle(mirect, getDXBrushForColour(System.Drawing.Color.Green));

                Vector2 centre = new Vector2(x + w / 2f, y + h / 2f);

                Ellipse eyeElipse = new Ellipse(centre, w / 2f, h / 2f);

                System.Drawing.Color overlapColour = magicEye.Colour;
                SharpDX.Direct2D1.Brush closedSectionBrush = getDXBrushForColour(System.Drawing.Color.FromArgb(255, (int)(overlapColour.R * 0.35f), (int)(overlapColour.G * 0.35f), (int)(overlapColour.B * 0.35f)), 255);
                System.Drawing.Color dimmedEye = System.Drawing.Color.FromArgb((int)(overlapColour.R * 0.75f), (int)(overlapColour.G * 0.75f), (int)(overlapColour.B * 0.75f));

                getPerc(magicEye, magicEye.Value, out float percX, out float percY, out PointF min, out PointF max);

                // scale percX for overlap hard coded for now
                percX *= 1f / 0.85f;

                float radiusX = w / 2f;
                float radiusY = h / 2f;

                if (percX <= 0.01f)
                {
                    // closed
                    _renderTarget.FillEllipse(eyeElipse, closedSectionBrush);
                }
                else if (percX >= 1f)// 0.99f)
                {
                    // fully open
                    _renderTarget.FillEllipse(eyeElipse, getDXBrushForColour(dimmedEye, 255));

                    if (percX >= 1f) 
                    {
                        // handle overlap by drawing a segment
                        float fRemaining = percX - 1f;

                        float fDeg = ((int)(360 * fRemaining)) / 2f;
                        float fRad = (float)degToRad(fDeg);

                        float endMaxX = centre.X + (float)Math.Sin(-fRad) * radiusX;
                        float endMaxY = centre.Y + (float)Math.Cos(-fRad) * radiusY;
                        float endMinX = centre.X + (float)Math.Sin(fRad) * radiusX;
                        float endMinY = centre.Y + (float)Math.Cos(fRad) * radiusY;

                        PathGeometry sharpGeometry = new PathGeometry(_renderTarget.Factory);

                        GeometrySink geo = sharpGeometry.Open();
                        geo.BeginFigure(new SharpDX.Vector2(centre.X, centre.Y), FigureBegin.Filled);

                        geo.AddLine(new SharpDX.Vector2(endMinX, endMinY));

                        ArcSegment arcSegment = new ArcSegment();
                        arcSegment.Point = new SharpDX.Vector2(endMaxX, endMaxY);
                        arcSegment.SweepDirection = SweepDirection.Clockwise;
                        arcSegment.ArcSize = fDeg <= 90f ? ArcSize.Small : ArcSize.Large;
                        arcSegment.Size = new Size2F(radiusX, radiusY);
                        geo.AddArc(arcSegment);

                        geo.EndFigure(FigureEnd.Closed); // adds the closing line
                        geo.Close();

                        _renderTarget.FillGeometry(sharpGeometry, getDXBrushForColour(overlapColour, 255)); // c being brightest

                        Utilities.Dispose(ref geo);
                        geo = null;
                        Utilities.Dispose(ref sharpGeometry);
                        sharpGeometry = null;
                    }

                    slits(centre, radiusX - (w * 0.1f), radiusY - (h * 0.1f), w, h, closedSectionBrush);
                }
                else
                {
                    _renderTarget.FillEllipse(eyeElipse, getDXBrushForColour(dimmedEye, 255));

                    // draw closed arc to cover
                    float fDeg = (360f - (int)(360 * percX)) / 2f;
                    float fRad = (float)degToRad(fDeg);

                    //yes sin is normally Y axis, but to save angle rotation swap them
                    float endMaxX = centre.X + (float)Math.Sin(-fRad) * radiusX;
                    float endMaxY = centre.Y + (float)Math.Cos(-fRad) * radiusY;
                    float endMinX = centre.X + (float)Math.Sin(fRad) * radiusX;
                    float endMinY = centre.Y + (float)Math.Cos(fRad) * radiusY;

                    PathGeometry sharpGeometry = new PathGeometry(_renderTarget.Factory);

                    GeometrySink geo = sharpGeometry.Open();
                    geo.BeginFigure(new SharpDX.Vector2(centre.X, centre.Y), FigureBegin.Filled);

                    geo.AddLine(new SharpDX.Vector2(endMinX, endMinY));

                    ArcSegment arcSegment = new ArcSegment();
                    arcSegment.Point = new SharpDX.Vector2(endMaxX, endMaxY);
                    arcSegment.SweepDirection = SweepDirection.Clockwise;
                    arcSegment.ArcSize = fDeg <= 90f ? ArcSize.Small : ArcSize.Large;
                    arcSegment.Size = new Size2F(radiusX, radiusY);
                    geo.AddArc(arcSegment);

                    geo.EndFigure(FigureEnd.Closed); // adds the closing line
                    geo.Close();

                    _renderTarget.FillGeometry(sharpGeometry, closedSectionBrush);

                    Utilities.Dispose(ref geo);
                    geo = null;
                    Utilities.Dispose(ref sharpGeometry);
                    sharpGeometry = null;

                    slits(centre, radiusX - (w * 0.1f), radiusY - (h * 0.1f), w, h, closedSectionBrush);
                }

                // centre cap/cover
                eyeElipse.RadiusX = w / 6f;
                eyeElipse.RadiusY = w / 6f;
                _renderTarget.FillEllipse(eyeElipse, getDXBrushForColour(System.Drawing.Color.FromArgb(32, 32, 32)));
            }
            private void renderText(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsText txt = (clsText)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                string sText;
                switch (txt.Text.ToLower())
                {
                    case "%group%":
                        sText = m.DisplayGroupText;
                        break;
                    //case "%fps%":
                    //    sText = _nFps.ToString();
                    //    break;
                    default:
                        sText = txt.Text;
                        break;
                }

                float fontSize;
                SizeF size;
                if (txt.FixedSize)
                {
                    fontSize = txt.FontSize;
                    size = measureString("0", txt.FontFamily, txt.Style, fontSize);
                }
                else
                {
                    fontSize = 72f;
                    size = measureString(sText, txt.FontFamily, txt.Style, fontSize);
                }

                float ratio = w / size.Width;
                float newSize = (float)Math.Round((fontSize * ratio) * (fontSize / _renderTarget.DotsPerInch.Width), 1);

                SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x, y, w, h);
                _renderTarget.DrawText(sText, getDXTextFormatForFont(txt.FontFamily, newSize, txt.Style), txtrect, getDXBrushForColour(txt.Colour, 255));
            }
            private void renderHBarMarkersOnly(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                // similar to renderHBar, but only renders the marker
                clsBarItem cbi = (clsBarItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                PointF min, max;
                float percX, percY;

                getPerc(cbi, cbi.Value, out percX, out percY, out min, out max);

                float xPos = x + (min.X * w) + (percX * ((max.X - min.X) * w));

                SharpDX.Direct2D1.Brush markerColour = getDXBrushForColour(cbi.MarkerColour, 255);
                SharpDX.Direct2D1.Brush peakHoldMarkerColour = getDXBrushForColour(cbi.PeakHoldMarkerColour, 255);

                float maxHistory_x = x;

                if (cbi.PeakHold) // max is used for peak hold
                {
                    getPerc(cbi, cbi.MaxHistory, out percX, out percY, out min, out max);
                    maxHistory_x = x + (min.X * w) + (percX * ((max.X - min.X) * w));
                }

                //peak hold marker
                if (cbi.PeakHold)
                    _renderTarget.DrawLine(new SharpDX.Vector2(maxHistory_x, y), new SharpDX.Vector2(maxHistory_x, y + h), peakHoldMarkerColour, cbi.StrokeWidth);

                //value marker
                if (cbi.ShowMarker)
                    _renderTarget.DrawLine(new SharpDX.Vector2(xPos, y), new SharpDX.Vector2(xPos, y + h), markerColour, cbi.StrokeWidth);
            }
            private clsMeterItem renderHBar(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsBarItem cbi = (clsBarItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                //SharpDX.RectangleF mirect = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.DrawRectangle(mirect, getDXBrushForColour(System.Drawing.Color.Red));

                PointF min, max;
                float percX, percY;

                getPerc(cbi, cbi.Value, out percX, out percY, out min, out max);

                float xPos = x + (min.X * w) + (percX * ((max.X - min.X) * w));

                float minHistory_x = x;
                float maxHistory_x = x;
                if (cbi.ShowHistory)
                {
                    getPerc(cbi, cbi.MinHistory, out percX, out percY, out min, out max);
                    minHistory_x = x + (min.X * w) + (percX * ((max.X - min.X) * w));
                    getPerc(cbi, cbi.MaxHistory, out percX, out percY, out min, out max);
                    maxHistory_x = x + (min.X * w) + (percX * ((max.X - min.X) * w));
                }
                else if (cbi.PeakHold) // max is used for peak hold
                {
                    getPerc(cbi, cbi.MaxHistory, out percX, out percY, out min, out max);
                    maxHistory_x = x + (min.X * w) + (percX * ((max.X - min.X) * w));
                }

                int segmentBlockSize = 7;
                int segmentStep = 9;
                float fHighXPosTransition = float.MaxValue;
                float fBarHeight = h * 0.85f; // same as base line % in render scale

                if (cbi.Style == clsBarItem.BarStyle.Segments || cbi.Style == clsBarItem.BarStyle.SolidFilled)
                {
                    segmentBlockSize = (int)(w * 0.02f);
                    if (segmentBlockSize < 7) segmentBlockSize = 7; // minimum 7 pixels
                    int segmentGapSize = (int)(segmentBlockSize * 0.2f);
                    if (segmentBlockSize < 2) segmentBlockSize = 2; // minimum 2 pixels

                    segmentStep = segmentBlockSize + segmentGapSize; // step, to include block + gap                  

                    if (!cbi.HighPoint.IsEmpty)
                    {
                        fHighXPosTransition = x + (w * cbi.HighPoint.X);
                    }
                }
                SharpDX.Direct2D1.Brush markerColour = getDXBrushForColour(cbi.MarkerColour, 255);
                SharpDX.Direct2D1.Brush peakValueColour = getDXBrushForColour(cbi.PeakValueColour, 255);
                SharpDX.Direct2D1.Brush historyColour = getDXBrushForColour(cbi.HistoryColour, cbi.HistoryColour.A);
                SharpDX.Direct2D1.Brush colour = getDXBrushForColour(cbi.Colour, 255);
                SharpDX.Direct2D1.Brush colourHigh = getDXBrushForColour(cbi.ColourHigh, 255);
                SharpDX.Direct2D1.Brush peakHoldMarkerColour = getDXBrushForColour(cbi.PeakHoldMarkerColour, 255);

                if (cbi.ShowHistory)
                {
                    switch (cbi.Style)
                    {
                        case clsBarItem.BarStyle.None:
                        case clsBarItem.BarStyle.SolidFilled:
                        case clsBarItem.BarStyle.Line:
                            {
                                SharpDX.RectangleF history = new SharpDX.RectangleF(minHistory_x, y, maxHistory_x - minHistory_x, fBarHeight);
                                _renderTarget.FillRectangle(history, historyColour);
                            }
                            break;
                        case clsBarItem.BarStyle.Segments:
                            {
                                int numValueBlocks = (int)((xPos - x) / (float)segmentStep);

                                float i;
                                float startX = (numValueBlocks * segmentStep) + x;
                                SharpDX.RectangleF barRect;

                                for (i = startX; i < maxHistory_x - segmentStep; i += segmentStep)
                                {
                                    barRect = new SharpDX.RectangleF(i, y, segmentBlockSize, fBarHeight);
                                    _renderTarget.FillRectangle(barRect, historyColour);
                                }

                                // complete the end sliver
                                if (i < maxHistory_x)
                                {
                                    barRect = new SharpDX.RectangleF(i, y, maxHistory_x - i, fBarHeight);
                                    _renderTarget.FillRectangle(barRect, historyColour);
                                }
                            }
                            break;
                    }
                }

                switch (cbi.Style)
                {
                    case clsBarItem.BarStyle.SolidFilled:
                        {
                            float fEnd = xPos < fHighXPosTransition ? xPos : fHighXPosTransition;

                            SharpDX.RectangleF barRect = new SharpDX.RectangleF(x, y, fEnd - x, fBarHeight);

                            _renderTarget.FillRectangle(barRect, colour);

                            if (fEnd < xPos) // the area in the high range
                            {
                                // complete the bar
                                barRect = new SharpDX.RectangleF(fEnd, y, xPos - fEnd, fBarHeight);
                                _renderTarget.FillRectangle(barRect, colourHigh);
                            }
                        }
                        break;
                    case clsBarItem.BarStyle.Line:
                        {
                            // marker is drawn below
                        }
                        break;
                    case clsBarItem.BarStyle.Segments:
                        {
                            float i;
                            SharpDX.RectangleF barRect;

                            float fEnd = xPos < fHighXPosTransition ? xPos : fHighXPosTransition;

                            for (i = x; i < fEnd - segmentStep; i += segmentStep)
                            {
                                barRect = new SharpDX.RectangleF(i, y, segmentBlockSize, fBarHeight);
                                _renderTarget.FillRectangle(barRect, colour);
                            }

                            // complete the end sliver up to the high transition
                            if (i < fEnd)
                            {
                                barRect = new SharpDX.RectangleF(i, y, fEnd - i, fBarHeight);
                                _renderTarget.FillRectangle(barRect, colour);
                            }

                            if (xPos > fHighXPosTransition) // the area in the high range
                            {
                                // sliver to complete block at high transition
                                if (i < fEnd)
                                {
                                    barRect = new SharpDX.RectangleF(fEnd, y, i + segmentBlockSize - fEnd, fBarHeight);
                                    _renderTarget.FillRectangle(barRect, colourHigh);
                                }

                                float j;
                                for (j = i + segmentStep; j < xPos - segmentStep; j += segmentStep)
                                {
                                    barRect = new SharpDX.RectangleF(j, y, segmentBlockSize, fBarHeight);
                                    _renderTarget.FillRectangle(barRect, colourHigh);
                                }

                                // complete the end sliver
                                if (j < xPos)
                                {
                                    barRect = new SharpDX.RectangleF(j, y, xPos - j, fBarHeight);
                                    _renderTarget.FillRectangle(barRect, colourHigh);
                                }
                            }
                        }
                        break;
                }

                //peak hold marker
                if (cbi.PeakHold)
                    _renderTarget.DrawLine(new SharpDX.Vector2(maxHistory_x, y), new SharpDX.Vector2(maxHistory_x, y + h), peakHoldMarkerColour, cbi.StrokeWidth);

                //value marker
                if (cbi.ShowMarker)
                    _renderTarget.DrawLine(new SharpDX.Vector2(xPos, y), new SharpDX.Vector2(xPos, y + h), markerColour, cbi.StrokeWidth);

                //value text
                if (cbi.ShowValue)
                {
                    float fontSizeEmScaled = (cbi.FontSize / 16f) * (rect.Width / 52f);

                    string sText;
                    switch (cbi.Unit)
                    {
                        case clsBarItem.Units.S_UNTS:
                            if (cbi.ReadingSource != Reading.ESTIMATED_PBSNR)
                                sText = Common.SMeterFromDBM(cbi.Value, MeterManager.IsAboveS9Frequency(_rx)).Replace(" ", "");
                            else
                                sText = (cbi.Value / 6f).ToString("f1") + "su";
                            break;
                        case clsBarItem.Units.U_V:
                            sText = Common.UVfromDBM(cbi.Value).ToString("f2") + "uV";
                            break;
                        default:
                            sText = cbi.Value.ToString("f1") + MeterManager.ReadingUnits(cbi.ReadingSource);
                            break;
                    }

                    SizeF szTextSize = measureString(sText, cbi.FontFamily, cbi.FntStyle, fontSizeEmScaled);
                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x, y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                    _renderTarget.DrawText(sText, getDXTextFormatForFont(cbi.FontFamily, fontSizeEmScaled, cbi.FntStyle), txtrect, markerColour);
                }
                if (cbi.ShowPeakValue)
                {
                    float fontSizeEmScaled = (cbi.FontSize / 16f) * (rect.Width / 52f);

                    string sText;
                    switch (cbi.Unit)
                    {
                        case clsBarItem.Units.S_UNTS:
                            if (cbi.ReadingSource != Reading.ESTIMATED_PBSNR)
                                sText = Common.SMeterFromDBM(cbi.MaxHistory, MeterManager.IsAboveS9Frequency(_rx)).Replace(" ", "");
                            else
                                sText = (cbi.Value / 6f).ToString("f1") + "su";
                            break;
                        case clsBarItem.Units.U_V:
                            sText = Common.UVfromDBM(cbi.MaxHistory).ToString("f2") + "uV";
                            break;
                        default:
                            sText = cbi.MaxHistory.ToString("f1") + MeterManager.ReadingUnits(cbi.ReadingSource);
                            break;
                    }
                    SizeF szTextSize = measureString(sText, cbi.FontFamily, cbi.FntStyle, fontSizeEmScaled);
                    SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x + w - szTextSize.Width, y - szTextSize.Height - (h * 0.1f), szTextSize.Width, szTextSize.Height);
                    _renderTarget.DrawText(sText, getDXTextFormatForFont(cbi.FontFamily, fontSizeEmScaled, cbi.FntStyle, true), txtrect, peakValueColour);
                }

                return cbi.PostDrawItem;
            }
            //private void renderVBar(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            //{
            //    clsBarItem cbi = (clsBarItem)mi;

            //    float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
            //    float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
            //    float w = rect.Width * (mi.Size.Width / m.XRatio);
            //    float h = rect.Height * (mi.Size.Height / m.YRatio);

            //    //SharpDX.RectangleF mirect = new SharpDX.RectangleF(x, y, w, h);
            //    //_renderTarget.DrawRectangle(mirect, getDXBrushForColour(System.Drawing.Color.Green));

            //    PointF min, max;
            //    float percX, percY;

            //    getPerc(cbi, cbi.Value, out percX, out percY, out min, out max);

            //    //top left is 0,0
            //    float yBottom = y + h;
            //    //float xPos = x + (min.X * w) + (percX * ((max.X - min.X) * w));
            //    float yPos = yBottom - ((min.Y * h) + (percY * ((max.Y - min.Y) * h)));

            //    float minHistory_y = y;
            //    float maxHistory_y = y;
            //    if (cbi.ShowHistory)
            //    {
            //        getPerc(cbi, cbi.MinHistory, out percX, out percY, out min, out max);
            //        minHistory_y = yBottom - ((min.Y * h) + (percY * ((max.Y - min.Y) * h)));
            //        getPerc(cbi, cbi.MaxHistory, out percX, out percY, out min, out max);
            //        maxHistory_y = yBottom - ((min.Y * h) + (percY * ((max.Y - min.Y) * h)));
            //    }
            //    else if (cbi.PeakHold) // max is used for peak hold
            //    {
            //        getPerc(cbi, cbi.MaxHistory, out percX, out percY, out min, out max);
            //        maxHistory_y = yBottom - ((min.Y * h) + (percY * ((max.Y - min.Y) * h)));
            //    }

            //    int segmentBlockSize = (int)(w * 0.02f);
            //    if (segmentBlockSize < 7) segmentBlockSize = 7;
            //    int segmentGapSize = (int)(segmentBlockSize * 0.2f);
            //    if (segmentBlockSize < 2) segmentBlockSize = 2;
            //    int segmentStep = segmentBlockSize + segmentGapSize;

            //    if (cbi.ShowHistory)
            //    {
            //        switch (cbi.Style)
            //        {
            //            case clsBarItem.BarStyle.None:
            //            case clsBarItem.BarStyle.SolidFilled:
            //            case clsBarItem.BarStyle.Line:
            //                {
            //                    SharpDX.RectangleF history = new SharpDX.RectangleF(x, maxHistory_y, w, minHistory_y - maxHistory_y); // drawn TL to BR
            //                    _renderTarget.FillRectangle(history, getDXBrushForColour(cbi.HistoryColour));
            //                }
            //                break;
            //            //case clsBarItem.BarStyle.Segments:
            //            //    {
            //            //        int numValueBlocks = (int)((xPos - x) / (float)segmentStep);

            //            //        float i;
            //            //        float startX = (numValueBlocks * segmentStep) + x;
            //            //        SharpDX.RectangleF barrect;
            //            //        for (i = startX; i < maxHistory_x - segmentStep; i += segmentStep)
            //            //        {
            //            //            barrect = new SharpDX.RectangleF(i, y, segmentBlockSize, h);
            //            //            _renderTarget.FillRectangle(barrect, getDXBrushForColour(cbi.HistoryColour));
            //            //        }

            //            //        // complete the sliver
            //            //        if (i < maxHistory_x)
            //            //        {
            //            //            barrect = new SharpDX.RectangleF(i, y, maxHistory_x - i, h);
            //            //            _renderTarget.FillRectangle(barrect, getDXBrushForColour(cbi.HistoryColour));
            //            //        }
            //            //    }
            //            //    break;
            //        }
            //    }

            //    switch (cbi.Style)
            //    {
            //        case clsBarItem.BarStyle.SolidFilled:
            //            {
            //                SharpDX.RectangleF barrect = new SharpDX.RectangleF(x, yPos, w, yBottom - yPos);

            //                if (cbi.PeakHold)
            //                    _renderTarget.DrawLine(new SharpDX.Vector2(x, maxHistory_y), new SharpDX.Vector2(x + w, maxHistory_y), getDXBrushForColour(cbi.PeakHoldMarkerColour), cbi.StrokeWidth);

            //                _renderTarget.FillRectangle(barrect, getDXBrushForColour(cbi.Colour));
            //            }
            //            break;
            //        case clsBarItem.BarStyle.Line:
            //            {
            //                if (cbi.PeakHold)
            //                    _renderTarget.DrawLine(new SharpDX.Vector2(x, maxHistory_y), new SharpDX.Vector2(x + w, maxHistory_y), getDXBrushForColour(cbi.PeakHoldMarkerColour), cbi.StrokeWidth);

            //                _renderTarget.DrawLine(new SharpDX.Vector2(x, yPos), new SharpDX.Vector2(x + w, yPos), getDXBrushForColour(cbi.MarkerColour), cbi.StrokeWidth);
            //            }
            //            break;
            //        //case clsBarItem.BarStyle.Segments:
            //        //    {
            //        //        float i;
            //        //        SharpDX.RectangleF barrect;
            //        //        for (i = x; i < xPos - segmentStep; i += segmentStep)
            //        //        {
            //        //            barrect = new SharpDX.RectangleF(i, y, segmentBlockSize, h);
            //        //            _renderTarget.FillRectangle(barrect, getDXBrushForColour(cbi.Colour));
            //        //        }

            //        //        // complete the sliver
            //        //        if (i < xPos)
            //        //        {
            //        //            barrect = new SharpDX.RectangleF(i, y, xPos - i, h);
            //        //            _renderTarget.FillRectangle(barrect, getDXBrushForColour(cbi.Colour));
            //        //        }

            //        //        if (cbi.PeakHold)
            //        //            _renderTarget.DrawLine(new SharpDX.Vector2(maxHistory_x, y), new SharpDX.Vector2(maxHistory_x, y + h), getDXBrushForColour(cbi.PeakHoldMarkerColour), cbi.StrokeWidth);

            //        //        if (cbi.ShowMarker)
            //        //            _renderTarget.DrawLine(new SharpDX.Vector2(xPos, y), new SharpDX.Vector2(xPos, y + h), getDXBrushForColour(cbi.MarkerColour), cbi.StrokeWidth);
            //        //    }
            //        //    break;
            //    }

            //    if (cbi.ShowValue)
            //    {
            //        string sText = cbi.Value.ToString("0.0") + MeterManager.ReadingUnits(cbi.ReadingSource);

            //        float fontSize = 38f;//cbi.FontSize; //38f;
            //        SizeF adjustedFontSize = measureString("0", cbi.FontFamily, cbi.FntStyle, fontSize);
            //        float ratio = h / adjustedFontSize.Height;
            //        float newSize = (float)Math.Round((fontSize * ratio) * (fontSize / _renderTarget.DotsPerInch.Width), 1);

            //        SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x, y + (h * 0.2f), w, h);
            //        _renderTarget.DrawText(sText, getDXTextFormatForFont(cbi.FontFamily, newSize, cbi.FntStyle), txtrect, getDXBrushForColour(cbi.FontColour));
            //    }
            //}
            private void renderSolidColour(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsSolidColour sc = (clsSolidColour)mi;
                if (!sc.Visible) return;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                SharpDX.RectangleF rectSC = new SharpDX.RectangleF(x, y, w, h);
                _renderTarget.FillRectangle(rectSC, getDXBrushForColour(sc.Colour, sc.Colour.A));
            }
            private void renderFadeCover(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                //clsFadeCover fc = (clsFadeCover)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                int nFade = 255 - fade(mi, m);

                float wi = w * 0.01f;
                float hi = h * 0.01f;
                wi = Math.Max(2f, wi);
                hi = Math.Max(2f, hi);

                SharpDX.RectangleF rectFC = new SharpDX.RectangleF(x, y, w, h);
                rectFC.Inflate(wi, hi); // increase size slightly as indictator lines stroke width of 3 will be outside bounds
                _renderTarget.FillRectangle(rectFC, getDXBrushForColour(this.BackgroundColour, nFade));
                //_renderTarget.DrawRectangle(rectFC, getDXBrushForColour(System.Drawing.Color.YellowGreen, nFade));
            }
            private void getParts(double vfoFreq, out string MHz, out string kHz, out string hz)
            {
                string vfo = vfoFreq.ToString("0.000000");
                int index = vfo.IndexOf(".");
                MHz = vfo.Substring(0, index);
                kHz = vfo.Substring(index + 1, 3);
                hz = vfo.Substring(index + 4, 3);
            }
            private (float, float) plotText(string sText, float x, float y, float h, float containerWidth, float fTextSize, System.Drawing.Color c, int nFade, string sFontFamily, FontStyle style, bool bAlignRight = false, bool bAlignCentre = false, float fit_size_width = 0, bool ignore_cache = false, float fit_size_height = 0, float rotate_deg = 0, bool fill_background = false, object back_colour = null)
            {
                if (string.IsNullOrEmpty(sText)) return (0, 0);

                float fontSizeEmScaled = (fTextSize / 16f) * (containerWidth / 52f);
                SizeF szTextSize;

                float ratio_w = 1f;
                float ratio_h = 1f;
                float ratio = 1f;
                szTextSize = measureString(sText, sFontFamily, style, fontSizeEmScaled, ignore_cache);
                if (fit_size_width > 0)
                {
                    // need to adjust the size by some ratio?
                    ratio_w = fit_size_width / szTextSize.Width;
                }
                if (fit_size_height > 0)
                {
                    // need to adjust the size by some ratio?
                    ratio_h = fit_size_height / szTextSize.Height;
                }

                if ((Math.Abs(1f - ratio_w)) > (Math.Abs(1f - ratio_h)))
                {
                    if(szTextSize.Height * ratio_w <= fit_size_height)
                        ratio = ratio_w;
                    else
                        ratio = ratio_h;
                }
                else
                {
                    if (szTextSize.Width * ratio_h <= fit_size_width)
                        ratio = ratio_h;
                    else
                        ratio = ratio_w;
                }

                if (ratio != 1f) {
                    fTextSize *= ratio;
                    fontSizeEmScaled = (fTextSize / 16f) * (containerWidth / 52f);
                    szTextSize = measureString(sText, sFontFamily, style, fontSizeEmScaled, ignore_cache);
                }


                SharpDX.RectangleF txtrect;
                if (!bAlignRight)
                {
                    if (bAlignCentre)
                    {
                        txtrect = new SharpDX.RectangleF(x - szTextSize.Width / 2f, y - szTextSize.Height / 2f, szTextSize.Width, szTextSize.Height);
                    }
                    else
                    {
                        txtrect = new SharpDX.RectangleF(x, y, szTextSize.Width, szTextSize.Height);
                    }
                }
                else
                {
                    // use x is now right edge
                    txtrect = new SharpDX.RectangleF(x - szTextSize.Width, y, szTextSize.Width, szTextSize.Height);
                }

                Matrix3x2 originalTransform = Matrix3x2.Identity;
                if (rotate_deg != 0) 
                {
                    Vector2 textCenter = new Vector2(txtrect.Width / 2, txtrect.Height / 2);
                    originalTransform = _renderTarget.Transform;
                    _renderTarget.Transform = originalTransform * Matrix3x2.Rotation(MathUtil.DegreesToRadians(rotate_deg), new Vector2(txtrect.Left, txtrect.Top) + textCenter);
                }
                if (fill_background)
                    _renderTarget.FillRectangle(txtrect, getDXBrushForColour((System.Drawing.Color)back_colour));

                _renderTarget.DrawText(sText, getDXTextFormatForFont(sFontFamily, fontSizeEmScaled, style), txtrect, getDXBrushForColour(c, nFade));
                if(rotate_deg != 0)
                {
                    _renderTarget.Transform = originalTransform;
                }
                //_renderTarget.DrawRectangle(txtrect, getDXBrushForColour(System.Drawing.Color.Red));

                return (szTextSize.Width, szTextSize.Height);
            }
            private void highlightBox(float x, float y, float w, float h, SharpDX.RectangleF rect, clsVfoDisplay vfo, int bx, int by, float gap, clsMeter m, float shift)
            {
                SharpDX.RectangleF rct;
                float wB = gap;
                float hB = h / 2f;
                float xB = x + (w * shift) + (bx * gap);
                float yB = y + (by * hB);

                rct = new SharpDX.RectangleF(xB, yB, wB, hB);
                _renderTarget.FillRectangle(rct, getDXBrushForColour(vfo.DigitHighlightColour));
            }
            private clsVfoDisplay.buttonState drawTuneStep(float x, float y, float w, float h, SharpDX.RectangleF rect, clsVfoDisplay vfo, clsMeter m, float shift, float x_multy)
            {
                bool vfoB = shift != 0 || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B;

                // draw grid
                SharpDX.Direct2D1.Brush lineBrush = getDXBrushForColour(System.Drawing.Color.White, 255);
                SharpDX.RectangleF rct;
                float xB = 0;
                float yB = y;

                float wB;
                if (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH)
                    wB = x + w * (0.5f - m.PadX - (m.PadX * 0.5f));
                else
                    wB = x + w * (1f - m.PadX);

                float hB = h;
                float gap = wB / 8f;
                int nx = 0;
                int ny = 0;
                float mx = 0;
                float my = 0;
                clsVfoDisplay.buttonState button_state = clsVfoDisplay.buttonState.NONE;
                int button_grid_index = -1;

                if (vfo.MouseEntered)
                {
                    mx = (vfo.MouseMovePoint.X - x) / w;
                    my = (vfo.MouseMovePoint.Y - y) / h;
                    mx = (mx - shift) / (wB / w);
                    if (mx >= 0 && mx <= 1 && my >= 0 && my <= 1)
                    {
                        nx = (int)(8 * mx);
                        ny = (int)(2 * my);
                        highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                        button_grid_index = (ny * 8) + nx;
                        button_state = clsVfoDisplay.buttonState.TUNE_STEP;
                    }
                }

                xB = x + (w * shift);
                rct = new SharpDX.RectangleF(xB, yB, wB, hB);
                _renderTarget.DrawRectangle(rct, lineBrush);
                _renderTarget.DrawLine(new RawVector2(xB, yB + hB / 2f), new RawVector2(xB + wB, yB + hB / 2f), lineBrush);
                for (int i = 1; i < 8; i++)
                {
                    _renderTarget.DrawLine(new RawVector2(xB + (gap * i), y), new RawVector2(xB + (gap * i), y + h), lineBrush);
                }

                nx = 0;
                ny = 0;
                float t_xB;
                float t_yB;

                int added = 0;
                for (int i = 0; i < vfo.TuneSteps.Count; i++)
                {
                    TuneStep ts = vfo.TuneSteps[i];

                    //skip these
                    if (ts.StepHz == 2 ||
                        ts.StepHz == 2000 ||
                        ts.StepHz == 2500 ||
                        ts.StepHz == 9000 ||
                        ts.StepHz == 15000 ||
                        ts.StepHz == 30000 ||
                        ts.StepHz == 250000 ||
                        ts.StepHz == 500000 ||
                        ts.StepHz == 1000000 ||
                        ts.StepHz == 10000000
                        ) continue;

                    //if (!vfoB) // same for now
                    //{
                        if (m.TuneStepIndex == i) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                    //}
                    //else
                    //{
                    //    if (m.TuneStepIndex == i) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                    //}

                    string skip_name = ts.Name.ToLower();
                    skip_name = skip_name.Replace("hz", "");

                    t_xB = xB + (gap / 2f) + (nx * gap);
                    t_yB = yB + (hB / 4f) + (ny * (hB / 2f));
                    plotText(skip_name, t_xB, t_yB, h, rect.Width * x_multy, 14f, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style, false, true, 0, true);

                    nx++;
                    if (nx > 7)
                    {
                        nx = 0;
                        ny += 1;
                    }
                    added++;
                    if (added > 16) break; // catch incase vfo.tunesteps.count less the skipped is > 16 boxes (8x2)
                }

                //plotText($"{mx.ToString("f3")},{my.ToString("f3")}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);

                if (vfoB)
                    vfo.ButtonGridIndexVFOb = button_grid_index;
                else
                    vfo.ButtonGridIndexVFOa = button_grid_index;

                return button_state;
            }
            private clsVfoDisplay.buttonState drawBand(float x, float y, float w, float h, SharpDX.RectangleF rect, clsVfoDisplay vfo, clsMeter m, float shift, float x_multy)
            {
                bool vfoB = shift != 0 || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B;

                // draw grid
                SharpDX.Direct2D1.Brush lineBrush = getDXBrushForColour(System.Drawing.Color.White, 255);
                SharpDX.RectangleF rct;
                float xB = 0;
                float yB = y;

                float wB;
                if(vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH)
                    wB = x + w * (0.5f - m.PadX - (m.PadX * 0.5f));
                else
                    wB = x + w * (1f - m.PadX);

                float hB = h;
                float gap = wB / 8f;
                int nx = 0;
                int ny = 0;
                float mx = 0;
                float my = 0;
                clsVfoDisplay.buttonState button_state = clsVfoDisplay.buttonState.NONE;
                int button_grid_index = -1;

                if (vfo.MouseEntered)
                {
                    mx = (vfo.MouseMovePoint.X - x) / w;
                    my = (vfo.MouseMovePoint.Y - y) / h;
                    mx = (mx - shift) / (wB / w);
                    if (mx >= 0 && mx <= 1 && my >= 0 && my <= 1)
                    {
                        nx = (int)(8 * mx);
                        ny = (int)(2 * my);
                        highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                        button_grid_index = (ny * 8) + nx;
                        button_state = clsVfoDisplay.buttonState.BAND;
                    }
                }

                xB = x + (w * shift);
                rct = new SharpDX.RectangleF(xB, yB, wB, hB);
                _renderTarget.DrawRectangle(rct, lineBrush);
                _renderTarget.DrawLine(new RawVector2(xB, yB + hB / 2f), new RawVector2(xB + wB, yB + hB / 2f), lineBrush);
                for (int i = 1; i < 8; i++)
                {
                    _renderTarget.DrawLine(new RawVector2(xB + (gap * i), y), new RawVector2(xB + (gap * i), y + h), lineBrush);
                }

                nx = 0;
                ny = 0;
                float t_xB;
                float t_yB;
                Band start = Band.FIRST;
                Band end = Band.LAST;
                bool vhf = false;
                if (_console.BandHFSelected)
                {
                    start = Band.B160M;
                    end = Band.B6M;
                }
                else if (_console.BandVHFSelected)
                {
                    start = Band.VHF0;
                    end = Band.VHF13;
                    vhf = true;
                }
                else if (_console.BandGENSelected)
                {
                    start = Band.B120M;
                    end = Band.B11M;
                }
                System.Drawing.Color band_colour;
                string band;
                for (int i = (int)start; i <= (int)end; i++)
                {
                    Band b = (Band)i;

                    if (!vfoB)
                    {
                        if (m.BandVfoA == (Band)i) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                    }
                    else
                    {
                        if (m.BandVfoB == (Band)i) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                    }

                    if (vhf)
                    {
                        int idx = (ny * 8) + nx;
                        if (_console.GetVHFEnabled(idx))
                            band_colour = BandStackManager.BandToColour(b);
                        else
                            band_colour = System.Drawing.Color.FromArgb(64, 64, 64);
                        
                        band = _console.GetVHFText(idx).Left(3);
                        if (string.IsNullOrEmpty(band))
                        {
                            band = BandStackManager.BandToString(b);
                            band = band.Substring(3);
                        }
                    }
                    else
                    {                        
                        band = BandStackManager.BandToString(b);
                        band = band.Left(band.Length - 1);
                        band_colour = BandStackManager.BandToColour(b);
                    }

                    t_xB = xB + (gap / 2f) + (nx * gap);
                    t_yB = yB + (hB / 4f) + (ny * (hB / 2f));
                    plotText(band, t_xB, t_yB, h, rect.Width * x_multy, 20f, band_colour, 255, vfo.FontFamily, vfo.Style, false, true, 0, true);
                    nx++;
                    if (nx > 7)
                    {
                        nx = 0;
                        ny += 1;
                    }
                }

                t_xB = xB + (gap / 2f) + (nx * gap);
                t_yB = yB + (hB / 4f) + (ny * (hB / 2f));
                if (_console.BandHFSelected)
                {
                    plotText("VHF", t_xB, t_yB, h, rect.Width * x_multy, 16f, System.Drawing.Color.Yellow, 255, vfo.FontFamily, vfo.Style, false, true, 0, true);
                    nx++;
                    t_xB = xB + (gap / 2f) + (nx * gap);
                    plotText("WWV", t_xB, t_yB, h, rect.Width * x_multy, 14f, BandStackManager.BandToColour(Band.WWV), 255, vfo.FontFamily, vfo.Style, false, true, 0, true);
                    nx++;
                    t_xB = xB + (gap / 2f) + (nx * gap);
                    plotText("SWL", t_xB, t_yB, h, rect.Width * x_multy, 16f, System.Drawing.Color.Orange, 255, vfo.FontFamily, vfo.Style, false, true, 0, true);
                }
                else if (_console.BandGENSelected || _console.BandVHFSelected)
                {
                    plotText("HF", t_xB, t_yB, h, rect.Width * x_multy, 16f, System.Drawing.Color.Red, 255, vfo.FontFamily, vfo.Style, false, true, 0, true);
                }

                //plotText($"{mx.ToString("f3")},{my.ToString("f3")}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);

                if (vfoB)
                    vfo.ButtonGridIndexVFOb = button_grid_index;
                else
                    vfo.ButtonGridIndexVFOa = button_grid_index;

                return button_state;
            }
            private clsVfoDisplay.buttonState drawMode(float x, float y, float w, float h, SharpDX.RectangleF rect, clsVfoDisplay vfo, clsMeter m, float shift, float x_multy)
            {
                bool vfoB = shift != 0 || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B;

                // draw grid
                SharpDX.Direct2D1.Brush lineBrush = getDXBrushForColour(System.Drawing.Color.White, 255);
                SharpDX.RectangleF rct;
                float xB = 0;
                float yB = y;

                //float wB = x + w * 0.47f * x_multy;
                float wB;
                if (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH)
                    wB = x + w * (0.5f - m.PadX - (m.PadX * 0.5f));
                else
                    wB = x + w * (1f - m.PadX);

                float hB = h;
                float gap = wB / 6f;
                int nx = 0;
                int ny = 0;
                float mx = 0;
                float my = 0;
                clsVfoDisplay.buttonState button_state = clsVfoDisplay.buttonState.NONE;
                int button_grid_index = -1;

                if (vfo.MouseEntered)
                {
                    mx = (vfo.MouseMovePoint.X - x) / w;
                    my = (vfo.MouseMovePoint.Y - y) / h;
                    mx = (mx - shift) / (wB / w);
                    if (mx >= 0 && mx <= 1 && my >= 0 && my <= 1)
                    {
                        nx = (int)(6 * mx);
                        ny = (int)(2 * my);
                        highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                        button_grid_index = (ny * 6) + nx;
                        button_state = clsVfoDisplay.buttonState.MODE;
                    }
                }

                xB = x + (w * shift);
                rct = new SharpDX.RectangleF(xB, yB, wB, hB);
                _renderTarget.DrawRectangle(rct, lineBrush);
                _renderTarget.DrawLine(new RawVector2(xB, yB + hB / 2f), new RawVector2(xB + wB, yB + hB / 2f), lineBrush);
                for (int i = 1; i < 6; i++)
                {
                    _renderTarget.DrawLine(new RawVector2(xB + (gap * i), y), new RawVector2(xB + (gap * i), y + h), lineBrush);
                }

                nx = 0;
                ny = 0;
                float t_xB;
                float t_yB;
                clsVfoDisplay.DSPModeForModeDisplay start = clsVfoDisplay.DSPModeForModeDisplay.LSB;
                clsVfoDisplay.DSPModeForModeDisplay end = clsVfoDisplay.DSPModeForModeDisplay.DRM;
                System.Drawing.Color mode_colour;
                string mode;
                for (int i = (int)start; i <= (int)end; i++)
                {
                    clsVfoDisplay.DSPModeForModeDisplay mde = (clsVfoDisplay.DSPModeForModeDisplay)i;
                    string modeString = mde.ToString();
                    Enum.TryParse<DSPMode>(modeString, out DSPMode dsp_mode);                    
                    if (!vfoB)
                    {
                        if (m.ModeVfoA == dsp_mode) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                    }
                    else
                    {
                        if (m.ModeVfoB == dsp_mode) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                    }

                    mode = mde.ToString();
                    mode_colour = System.Drawing.Color.White;

                    t_xB = xB + (gap / 2f) + (nx * gap);
                    t_yB = yB + (hB / 4f) + (ny * (hB / 2f));
                    plotText(mode, t_xB, t_yB, h, rect.Width * x_multy, 18f, mode_colour, 255, vfo.FontFamily, vfo.Style, false, true, 0, true);
                    nx++;
                    if (nx > 5)
                    {
                        nx = 0;
                        ny += 1;
                    }
                }

                //plotText($"{mx.ToString("f3")},{my.ToString("f3")}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);

                if (vfoB)
                    vfo.ButtonGridIndexVFOb = button_grid_index;
                else
                    vfo.ButtonGridIndexVFOa = button_grid_index;

                return button_state;
            }
            private clsVfoDisplay.buttonState drawFilter(float x, float y, float w, float h, SharpDX.RectangleF rect, clsVfoDisplay vfo, clsMeter m, float shift, float x_multy)
            {
                bool vfoB = shift != 0 || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B;

                // draw grid
                SharpDX.Direct2D1.Brush lineBrush = getDXBrushForColour(System.Drawing.Color.White, 255);
                SharpDX.RectangleF rct;
                float xB = 0;
                float yB = y;

                //float wB = x + w * 0.47f * x_multy;
                float wB;
                if (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH)
                    wB = x + w * (0.5f - m.PadX - (m.PadX * 0.5f));
                else
                    wB = x + w * (1f - m.PadX);

                float hB = h;
                float gap = wB / 6f;
                int nx = 0;
                int ny = 0;
                float mx = 0;
                float my = 0;
                clsVfoDisplay.buttonState button_state = clsVfoDisplay.buttonState.NONE;
                int button_grid_index = -1;

                if (vfo.MouseEntered)
                {
                    mx = (vfo.MouseMovePoint.X - x) / w;
                    my = (vfo.MouseMovePoint.Y - y) / h;
                    mx = (mx - shift) / (wB / w);
                    if (mx >= 0 && mx <= 1 && my >= 0 && my <= 1)
                    {
                        nx = (int)(6 * mx);
                        ny = (int)(2 * my);
                        highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                        button_grid_index = (ny * 6) + nx;
                        button_state = clsVfoDisplay.buttonState.FILTER;
                    }
                }

                xB = x + (w * shift);
                rct = new SharpDX.RectangleF(xB, yB, wB, hB);
                _renderTarget.DrawRectangle(rct, lineBrush);
                _renderTarget.DrawLine(new RawVector2(xB, yB + hB / 2f), new RawVector2(xB + wB, yB + hB / 2f), lineBrush);
                for (int i = 1; i < 6; i++)
                {
                    _renderTarget.DrawLine(new RawVector2(xB + (gap * i), y), new RawVector2(xB + (gap * i), y + h), lineBrush);
                }

                nx = 0;
                ny = 0;
                float t_xB;
                float t_yB;
                Filter start = Filter.F1;
                Filter end;
                if (!vfoB)
                    end = Filter.VAR2;
                else
                {
                    if (m.RX2Enabled)
                        end = Filter.F7;
                    else
                        end = Filter.VAR2;
                }
                System.Drawing.Color filter_colour;
                string filter;
                for (int i = (int)start; i <= (int)end; i++)
                {
                    Filter fltr = (Filter)i;
                    if (!vfoB)
                    {
                        if (m.FilterVfoA == fltr) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                        filter = _console.rx1_filters[(int)m.ModeVfoA].GetName(fltr);
                    }
                    else
                    {
                        if (m.FilterVfoB == fltr) highlightBox(x, y, w, h, rect, vfo, nx, ny, gap, m, shift);
                        if(m.RX2Enabled)
                            filter = _console.rx2_filters[(int)m.ModeVfoB].GetName(fltr);
                        else
                            filter = _console.rx1_filters[(int)m.ModeVfoA].GetName(fltr); // uses RX1
                    }
                    if (string.IsNullOrEmpty(filter)) filter = fltr.ToString();

                    filter_colour = System.Drawing.Color.White;

                    t_xB = xB + (gap / 2f) + (nx * gap);
                    t_yB = yB + (hB / 4f) + (ny * (hB / 2f));
                    plotText(filter, t_xB, t_yB, h, rect.Width * x_multy, 16f, filter_colour, 255, vfo.FontFamily, vfo.Style, false, true, gap, true);
                    nx++;
                    if (nx > 5)
                    {
                        nx = 0;
                        ny += 1;
                    }
                    // to add the VAR1/2 to rx2
                    if(m.RX2Enabled && vfoB && i == (int)Filter.F7)
                    {
                        start = Filter.VAR1;
                        end = Filter.VAR2;
                        i = (int)Filter.F10; // as i gets incremented and moved to VAR1
                    }
                }

                //plotText($"{mx.ToString("f3")},{my.ToString("f3")}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);

                if (vfoB)
                    vfo.ButtonGridIndexVFOb = button_grid_index;
                else
                    vfo.ButtonGridIndexVFOa = button_grid_index;

                return button_state;
            }
            private SharpDX.RectangleF shrinkRectangle(SharpDX.RectangleF original, float ratio, float absolute = 0f)
            {
                float newWidth = original.Width * ratio;
                float newHeight = original.Height * ratio;

                newWidth -= absolute;
                newHeight -= absolute;
                if (newWidth < 0) newWidth = 0;
                if (newHeight < 0) newHeight = 0;

                float offsetX = (original.Width - newWidth) / 2f;
                float offsetY = (original.Height - newHeight) / 2f;

                SharpDX.RectangleF shrunkRectangle = new SharpDX.RectangleF(
                    original.X + offsetX,
                    original.Y + offsetY,
                    newWidth,
                    newHeight
                );

                return shrunkRectangle;
            }
            private void drawRoundedRectangle(RoundedRectangle rr, SharpDX.Direct2D1.Brush b, float stroke)
            {
                if(rr.RadiusX>0 || rr.RadiusY>0)
                    _renderTarget.DrawRoundedRectangle(rr, b, stroke);
                else
                    _renderTarget.DrawRectangle(rr.Rect, b, stroke);
            }
            private void fillRoundedRectangle(RoundedRectangle rr, SharpDX.Direct2D1.Brush b)
            {
                if (rr.RadiusX > 0 || rr.RadiusY > 0)
                    _renderTarget.FillRoundedRectangle(rr, b);
                else
                    _renderTarget.FillRectangle(rr.Rect, b);
            }
            private void drawSafeLine(RawVector2 start, RawVector2 end, SharpDX.Direct2D1.Brush b, float width)
            {
                float start_x = start.X;
                float start_y = start.Y;
                float end_x = end.X;
                float end_y = end.Y;

                if (end.X < start_x) end.X = start.X;
                if (end.Y < start_y) end.Y = start.Y;

                _renderTarget.DrawLine(new RawVector2(start_x, start_y), new RawVector2(end_x, end_y), b, width);
            }
            private void renderButtonBox(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsButtonBox bb = mi as clsButtonBox;
                if (bb.Columns <= 0) return;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                //SharpDX.RectangleF rectSC = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.FillRectangle(rectSC, getDXBrushForColour(System.Drawing.Color.Green));
                int total_buttons = 0;
                for(int i = 0; i < bb.Buttons; i++)
                {
                    if (bb.GetVisible(1, i)) total_buttons++;
                }
                if (total_buttons == 0) return; // nothing visible

                int rows = total_buttons / bb.Columns;
                int overflow = total_buttons % bb.Columns;
                if (overflow > 0) rows++;
                int buttons_per_row = bb.Columns;

                float wh = w >= h ? w : h; // base margin/border/radius scales on largest
                float border = bb.Border * wh;
                float half_border = border / 2f;
                float radius = bb.Radius * wh;
                float margin = bb.Margin * wh;
                float expand = (bb.Margin * wh) - half_border; //expand so that last margin is not shown
                float button_width = ((w + expand) / (float)bb.Columns) - margin - border;
                float button_height = ((h + expand) / (float)rows) - margin - border;

                button_width += margin + border;
                button_height += margin + border;

                int highlighted_index = -1;
                int button_index = 0;

                PointF mouse = new PointF(-1, -1);
                if (bb.MouseEntered)
                {
                    mouse.X = bb.MouseMovePoint.X / w;
                    mouse.Y = bb.MouseMovePoint.Y / h;
                }

                for(int row  = 0; row < rows; row++)
                {
                    int col = 0;
                    while(col < buttons_per_row)
                    {
                        if (!bb.GetVisible(1, button_index))
                        {
                            button_index++;
                            if (button_index >= bb.Buttons) break;
                            continue; // skip an invisible button
                        }

                        bool indicator = bb.GetUseIndicator(1, button_index);
                        float xP = x + half_border + (button_width * col);
                        float yP = y + half_border + (button_height * row);

                        SharpDX.RectangleF rectBB = new SharpDX.RectangleF(xP, yP, button_width - margin - half_border, button_height - margin - half_border);

                        RoundedRectangle rr = new RoundedRectangle
                        {
                            Rect = rectBB,
                            RadiusX = radius,
                            RadiusY = radius
                        };

                        // luminance
                        System.Drawing.Color bg_colour = bb.GetFillColour(1, button_index);
                        System.Drawing.Color text_colour = bb.GetFontColour(1, button_index);
                        System.Drawing.Color hover_colour = bb.GetHoverColour(1, button_index);
                        System.Drawing.Color on_colour = bb.GetOnColour(1, button_index);
                        System.Drawing.Color off_colour = bb.GetOffColour(1, button_index);
                        System.Drawing.Color click_colour = bb.GetClickColour(1, button_index);

                        bool on = bb.GetOn(1, button_index);
                        System.Drawing.Color actual_bg;
                        if (!indicator)
                        {
                            if (on)
                                actual_bg = on_colour;
                            else
                            {
                                if (bb.GetUseOffColour(1, button_index))
                                    actual_bg = off_colour;
                                else
                                    actual_bg = bg_colour;
                            }
                        }
                        else
                            actual_bg = bg_colour;

                        text_colour = adjustTextColourForContrast(text_colour, actual_bg);

                        // fill
                        fillRoundedRectangle(rr, getDXBrushForColour(bg_colour));

                        //indicator disabled
                        if (!indicator)
                        {
                            if (on)
                                fillRoundedRectangle(rr, getDXBrushForColour(bb.GetOnColour(1, button_index), 255));
                            else
                            {
                                if(bb.GetUseOffColour(1, button_index))
                                    fillRoundedRectangle(rr, getDXBrushForColour(bb.GetOffColour(1, button_index), 255));
                            }
                        }

                        //click - change the highlight
                        if(bb.MouseEntered)
                        {
                            if (bb.ClickHighlight) hover_colour = click_colour;
                        }

                        // mouse highlight
                        if (bb.MouseEntered && rectBB.Contains((float)bb.MouseMovePoint.X, (float)bb.MouseMovePoint.Y))
                        {
                            fillRoundedRectangle(rr, getDXBrushForColour(hover_colour, 192));
                            highlighted_index = button_index;
                        }

                        //border
                        drawRoundedRectangle(rr, getDXBrushForColour(bb.GetBorderColour(1, button_index)), bb.Border * w);

                        //indicator
                        SharpDX.RectangleF indicator_adjust = new SharpDX.RectangleF(0, 0, 0, 0);
                        float text_size_modifier = 0.9f; // text gets shrunk slightly if no indicator ring is in use
                        if (indicator)
                        {                                                        
                            System.Drawing.Color indicator_colour = System.Drawing.Color.Transparent;
                            bool indicator_draw = false;

                            if(bb.GetOn(1, button_index))
                            {
                                indicator_colour = bb.GetOnColour(1, button_index);
                                indicator_draw = true;
                            }
                            else
                            {
                                if (bb.GetUseOffColour(1, button_index))
                                {
                                    //if (highlighted_index != button_index)
                                    //{
                                        indicator_colour = bb.GetOffColour(1, button_index);
                                        indicator_draw = true;
                                    //}
                                }
                            }

                            if (indicator_draw)
                            {
                                float indicator_width = bb.GetIndicatorWidth(1, button_index) * wh;
                                float indicator_shrink;
                                switch (bb.GetIndicatorType(1, button_index))
                                {
                                    case clsButtonBox.IndicatorType.BAR_LEFT:
                                        text_size_modifier = 0.9f;
                                        indicator_shrink = (0.015f * wh);
                                        drawSafeLine(new RawVector2(rectBB.Left + indicator_shrink + (indicator_width / 2f), rectBB.Top + indicator_shrink + (radius * 0.45f)), new RawVector2(rectBB.Left + indicator_shrink + (indicator_width / 2f), rectBB.Bottom - indicator_shrink - (radius * 0.45f)), getDXBrushForColour(indicator_colour), indicator_width);
                                        //indicator_adjust.Left = indicator_width + (indicator_shrink * 1.5f);
                                        indicator_adjust.Left = indicator_width;
                                        break;
                                    case clsButtonBox.IndicatorType.BAR_RIGHT:
                                        text_size_modifier = 0.9f;
                                        indicator_shrink = (0.015f * wh);
                                        drawSafeLine(new RawVector2(rectBB.Right - indicator_shrink - (indicator_width / 2f), rectBB.Top + indicator_shrink + (radius * 0.45f)), new RawVector2(rectBB.Right - indicator_shrink - (indicator_width / 2f), rectBB.Bottom - indicator_shrink - (radius * 0.45f)), getDXBrushForColour(indicator_colour), indicator_width);
                                        indicator_adjust.Right = indicator_width;
                                        //indicator_adjust.Right = indicator_width + (indicator_shrink * 1.5f);
                                        break;
                                    case clsButtonBox.IndicatorType.BAR_TOP:
                                        text_size_modifier = 0.9f;
                                        indicator_shrink = (0.01f * wh);
                                        drawSafeLine(new RawVector2(rectBB.Left + (indicator_shrink * 2f) + (radius * 0.45f), rectBB.Top + indicator_shrink + (indicator_width / 2f)), new RawVector2(rectBB.Right - (indicator_shrink * 2f) - (radius * 0.45f), rectBB.Top + indicator_shrink + (indicator_width / 2f)), getDXBrushForColour(indicator_colour), indicator_width);
                                        indicator_adjust.Top = indicator_width;
                                        break;
                                    case clsButtonBox.IndicatorType.BAR_BOTTOM:
                                        text_size_modifier = 0.9f;
                                        indicator_shrink = (0.01f * wh);
                                        drawSafeLine(new RawVector2(rectBB.Left + (indicator_shrink * 2f) + (radius * 0.45f), rectBB.Bottom - indicator_shrink - (indicator_width / 2f)), new RawVector2(rectBB.Right - (indicator_shrink * 2f) - (radius * 0.45f), rectBB.Bottom - indicator_shrink - (indicator_width / 2f)), getDXBrushForColour(indicator_colour), indicator_width);
                                        indicator_adjust.Bottom = indicator_width;
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_LEFT:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Left + indicator_shrink + (indicator_width / 2f), rectBB.Top + ((rectBB.Bottom - rectBB.Top) / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Left = (indicator_width * 1.1f) + (indicator_shrink * 1.5f);
                                            indicator_adjust.Right = indicator_shrink + radius * 0.45f;
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_RIGHT:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Right - indicator_shrink - (indicator_width / 2f), rectBB.Top + ((rectBB.Bottom - rectBB.Top) / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Left = indicator_shrink + radius * 0.45f;
                                            indicator_adjust.Right = (indicator_width * 1.1f) + (indicator_shrink * 1.5f);                                            
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_TOP:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Left + (rectBB.Right - rectBB.Left) / 2f, rectBB.Top + indicator_shrink + (indicator_width / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Top = (indicator_width * 0.8f) + (indicator_shrink * 1.5f);
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_BOTTOM:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Left + (rectBB.Right - rectBB.Left) / 2f, rectBB.Bottom - indicator_shrink - (indicator_width / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Bottom = (indicator_width * 0.8f) + (indicator_shrink * 1.5f);
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_TOP_LEFT:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Left + indicator_shrink + (indicator_width / 2f), rectBB.Top + indicator_shrink + (indicator_width / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Left = indicator_width + (indicator_shrink * 1.25f);
                                            indicator_adjust.Right = indicator_shrink + radius * 0.45f;
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_BOTTOM_LEFT:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Left + indicator_shrink + (indicator_width / 2f), rectBB.Bottom - indicator_shrink - (indicator_width / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Left = indicator_width + (indicator_shrink * 1.25f);
                                            indicator_adjust.Right = indicator_shrink + radius * 0.45f;
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_TOP_RIGHT:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Right - indicator_shrink - (indicator_width / 2f), rectBB.Top + indicator_shrink + (indicator_width / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Left = indicator_shrink + radius * 0.45f;
                                            indicator_adjust.Right = indicator_width + (indicator_shrink * 1.25f);
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.DOT_BOTTOM_RIGHT:
                                        {
                                            float rad = Math.Min((indicator_width / 2f) - (radius * 0.2f), ((rectBB.Bottom - rectBB.Top) / 2f) * 0.9f);
                                            rad = Math.Max(0f, rad);
                                            text_size_modifier = 1f;
                                            indicator_shrink = (0.01f * wh);
                                            Ellipse dot = new Ellipse(new RawVector2(rectBB.Right - indicator_shrink - (indicator_width / 2f), rectBB.Bottom - indicator_shrink - (indicator_width / 2f)), rad, rad);
                                            _renderTarget.FillEllipse(dot, getDXBrushForColour(indicator_colour));
                                            indicator_adjust.Left = indicator_shrink + radius * 0.45f;
                                            indicator_adjust.Right = indicator_width + (indicator_shrink * 1.25f);
                                        }
                                        break;
                                    case clsButtonBox.IndicatorType.RING:
                                    default:
                                        text_size_modifier = 0.85f;
                                        indicator_shrink = (0.02f * wh) + border + indicator_width;
                                        rr.Rect = shrinkRectangle(rectBB, 1f, indicator_shrink);
                                        drawRoundedRectangle(rr, getDXBrushForColour(indicator_colour), indicator_width);
                                        indicator_adjust.Top = indicator_width;
                                        indicator_adjust.Left = indicator_width;
                                        indicator_adjust.Right = indicator_width;
                                        indicator_adjust.Bottom = indicator_width;
                                        break;
                                }
                            }
                        }

                        //text
                        string text = bb.GetText(1, button_index);
                        if (!string.IsNullOrEmpty(text))
                        {
                            //rectBB = shrinkRectangle(rectBB, 1f, border + (indicator_width * 2f));
                            rectBB.Top += indicator_adjust.Top;
                            rectBB.Left += indicator_adjust.Left;
                            rectBB.Right -= indicator_adjust.Right;
                            rectBB.Bottom -= indicator_adjust.Bottom;
                            float cx = rectBB.Left + (rectBB.Width / 2f);
                            float cy = rectBB.Top + (rectBB.Height / 2f);
                            if (rectBB.Width > 0 && rectBB.Height > 0)
                            {
                                plotText(text, cx + (bb.FontShiftX * wh / (float)(buttons_per_row * 2f)), cy + (bb.FontShiftY * wh / (float)(buttons_per_row * 2f)), h, rect.Width, bb.GetFontSize(1, button_index), text_colour, 255, bb.GetFontFamily(1, button_index), bb.GetFontStyle(1, button_index), false, true, rectBB.Width * text_size_modifier * bb.FontScale, true, rectBB.Height * text_size_modifier * bb.FontScale);
                            }
                        }

                        col++;
                        button_index++;
                        if (button_index >= bb.Buttons) break;
                    }
                    if (button_index >= bb.Buttons) break;
                }

                if (bb.MouseEntered)
                {
                    //plotText($"{mouse.ToString()}", 0, 0, h, w, 30f, System.Drawing.Color.White, 255, "Trebuchet MS", FontStyle.Regular);
                    bb.ButtonIndex = highlighted_index;
                }
                else if(bb.ButtonIndex != -1)
                {
                    bb.ButtonIndex = -1;
                }
            }
            private System.Drawing.Color adjustTextColourForContrast(System.Drawing.Color textColor, System.Drawing.Color backgroundColor)
            {
                double backgroundLuminance = Common.GetLuminance(backgroundColor);
                double textLuminance = Common.GetLuminance(textColor);

                double contrastRatio = calculateContrastRatio(backgroundLuminance, textLuminance);

                if (contrastRatio < 2)
                {
                    //return backgroundLuminance > 0.5 ? System.Drawing.Color.Black : System.Drawing.Color.White;
                    return backgroundLuminance > 0.5 ? ControlPaint.Dark(textColor) : ControlPaint.Light(textColor);
                }
                
                return textColor;
            }

            //private double calculateLuminance(System.Drawing.Color color)
            //{
            //    double r = color.R / 255.0;
            //    double g = color.G / 255.0;
            //    double b = color.B / 255.0;

            //    r = (r <= 0.03928) ? r / 12.92 : Math.Pow((r + 0.055) / 1.055, 2.4);
            //    g = (g <= 0.03928) ? g / 12.92 : Math.Pow((g + 0.055) / 1.055, 2.4);
            //    b = (b <= 0.03928) ? b / 12.92 : Math.Pow((b + 0.055) / 1.055, 2.4);

            //    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
            //}

            private double calculateContrastRatio(double luminance1, double luminance2)
            {
                double lighter = Math.Max(luminance1, luminance2);
                double darker = Math.Min(luminance1, luminance2);

                return (lighter + 0.05) / (darker + 0.05);
            }

            private void renderVfoDisplay(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsVfoDisplay vfo = (clsVfoDisplay)mi;

                float x_shift = 0;
                switch (vfo.VFODispMode)
                {
                    case clsVfoDisplay.VFODisplayMode.VFO_B:
                        x_shift = 0.50f;
                        break;
                    case clsVfoDisplay.VFODisplayMode.VFO_BOTH:
                        x_shift = -0.01f; // -ve to add some shift to push beyond the gap between a + b
                        break;
                }
                float x_multy = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH ? 1f : 2.07f; // 0.07 to take into consideration the gap that was between a + b
                bool disp_a = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_A || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH;
                bool disp_b = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                int nVfoAFade = 255;
                int nVfoBFade = 255;
                if (m.RX == 1)
                {
                    if (m.RX2Enabled && !(m.MultiRxEnabled || m.Split)) nVfoBFade = 24; // vfoB is 'disabled' on rx1 if rx2 in use, but not if multirxenabled/splt
                }
                else
                {
                    if (m.RX2Enabled) nVfoAFade = 24; // vfoA is 'disabled' on rx2 if rx2 in use always
                }

                //subtract 0.03f from h if showing text, as the vfo expands, but everything below is based on non expanded
                if (vfo.ShowBandText)
                    h = rect.Height * ((mi.Size.Height - (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH ? 0.03f : 0.03f * 2f)) / m.YRatio);

                //mouse wheel boxes
                bool draw_box = false;
                bool button_back_box = false;
                double step = 0;
                float shift = 0;
                float xB = 0;
                float yB = 0;
                float wB = 0;
                float hB = 0;
                float mx = 0;
                float my = 0;
                SharpDX.RectangleF rct;
                bool mouse_over_good = false;

                clsVfoDisplay.buttonState button_state_vfoA = clsVfoDisplay.buttonState.NONE;
                clsVfoDisplay.buttonState button_state_vfoB = clsVfoDisplay.buttonState.NONE;

                if (vfo.MouseEntered)
                {
                    mx = (vfo.MouseMovePoint.X - x) / w;
                    my = (vfo.MouseMovePoint.Y - y) / h;
                    mx /= x_multy;

                    float box_size = 0;
                    int boxes = -1;
                    int box = -1;
                    float tx = -1;
                    shift = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH && mx >= 0.5f ? 0.510f : 0; //vfoA to B shift
                    mouse_over_good = true;
                    bool left = (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_A) && vfo.VFOARenderState == clsVfoDisplay.renderState.VFO && (shift == 0) && (m.RX == 1);
                    bool right = (vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B) && vfo.VFOBRenderState == clsVfoDisplay.renderState.VFO && (shift != 0 || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B) && ((m.RX == 1 && (!m.RX2Enabled || (m.Split || m.MultiRxEnabled))) || (m.RX == 2));

                    // band
                    if (my >= 0.555 && my <= 0.900 && (((mx >= 0.333 && mx <= 0.480) & left) || ((mx >= 0.333 + shift && mx <= 0.480 + shift) & right)))
                    {
                        xB = 0.333f + shift;
                        yB = 0.555f;
                        wB = 0.480f - 0.333f;
                        hB = 0.900f - 0.555f;

                        button_back_box = true;

                        if (left)
                        {
                            if (vfo.MouseButtonDown && vfo.MouseDownLong) 
                                vfo.PopBandStack();
                            else
                                button_state_vfoA = clsVfoDisplay.buttonState.BAND_SCREEN;
                            left = false;                            
                        }
                        if (right) 
                        {
                            //if (vfo.MouseButtonDown && vfo.MouseDownLong) // ignore vfoB for now
                            //    vfo.PopBandStack();
                            //else
                            button_state_vfoB = clsVfoDisplay.buttonState.BAND_SCREEN;
                            right = false;
                        }
                    }
                    // mode
                    if (my >= 0.555 && my <= 0.900 && (((mx >= 0.008 && mx <= 0.078) & left) || ((mx >= 0.008 + shift && mx <= 0.078 + shift) & right)))
                    {
                        xB = 0.008f + shift;
                        yB = 0.530f;
                        wB = 0.078f - 0.008f;
                        hB = 0.900f - 0.555f;

                        button_back_box = true;

                        if (left)
                        {
                            button_state_vfoA = clsVfoDisplay.buttonState.MODE_SCREEN;
                            left = false;
                        }
                        if (right)
                        {
                            button_state_vfoB = clsVfoDisplay.buttonState.MODE_SCREEN;
                            right = false;
                        }
                    }
                    // filter
                    if (my >= 0.555 && my <= 0.900 && (((mx >= 0.250 && mx <= 0.332) & left) || ((mx >= 0.250 + shift && mx <= 0.332 + shift) & right)))
                    {
                        xB = 0.250f + shift;
                        yB = 0.555f;
                        wB = 0.332f - 0.250f;
                        hB = 0.900f - 0.555f;

                        button_back_box = true;

                        if (left)
                        {
                            button_state_vfoA = clsVfoDisplay.buttonState.FILTER_SCREEN;
                            left = false;
                        }
                        if (right)
                        {
                            button_state_vfoB = clsVfoDisplay.buttonState.FILTER_SCREEN;
                            right = false;
                        }
                    }
                    //split vfoA only
                    if (my >= 0.030 && my <= 0.430 && (((mx >= 0.100 && mx <= 0.200) & left)))
                    {
                        xB = 0.100f + shift;
                        yB = 0.030f;
                        wB = 0.200f - 0.100f;
                        hB = 0.430f - 0.030f;

                        button_back_box = true;

                        if (left)
                        {
                            button_state_vfoA = clsVfoDisplay.buttonState.SPLIT;
                            left = false;
                        }
                        if (right)
                        {
                            button_state_vfoB = clsVfoDisplay.buttonState.SPLIT;
                            right = false;
                        }
                    }
                    //tx
                    if (my >= 0.520 && my <= 0.920 && (((mx >= 0.152 && mx <= 0.200) & left) || ((mx >= 0.152 + shift && mx <= 0.200 + shift) & right)))
                    {
                        xB = 0.152f + shift;
                        yB = 0.520f;
                        wB = 0.200f - 0.152f;
                        hB = 0.920f - 0.520f;

                        button_back_box = true;

                        if (left)
                        {
                            button_state_vfoA = clsVfoDisplay.buttonState.TX;
                            left = false;
                        }
                        if (right)
                        {
                            button_state_vfoB = clsVfoDisplay.buttonState.TX;
                            right = false;
                        }
                    }
                    //lock
                    if (my >= 0.520 && my <= 0.920 && (((mx >= 0.200 && mx <= 0.223) & left) || ((mx >= 0.200 + shift && mx <= 0.223 + shift) & right)))
                    {
                        xB = 0.200f + shift;
                        yB = 0.520f;
                        wB = 0.223f - 0.200f;
                        hB = 0.920f - 0.520f;

                        button_back_box = true;

                        if (left)
                        {
                            button_state_vfoA = clsVfoDisplay.buttonState.LOCK;
                            left = false;
                        }
                        if (right)
                        {
                            button_state_vfoB = clsVfoDisplay.buttonState.LOCK;
                            right = false;
                        }
                    }
                    //sync
                    if (my >= 0.520 && my <= 0.920 && (((mx >= 0.224 && mx <= 0.250) & left) || ((mx >= 0.224 + shift && mx <= 0.250 + shift) & right)))
                    {
                        xB = 0.224f + shift;
                        yB = 0.520f;
                        wB = 0.250f - 0.224f;
                        hB = 0.920f - 0.520f;

                        button_back_box = true;

                        if (left)
                        {
                            button_state_vfoA = clsVfoDisplay.buttonState.VFO_SYNC;
                            left = false;
                        }
                        if (right)
                        {
                            button_state_vfoB = clsVfoDisplay.buttonState.VFO_SYNC;
                            right = false;
                        }
                    }
                    // large numbers
                    if (my >= 0.100 && my <= 0.530)
                    {
                        //mhz
                        if (((mx >= 0.216 && mx <= 0.330) && left) || ((mx >= 0.216 + shift && mx <= 0.330 + shift) && right))
                        {
                            yB = 0.100f;
                            hB = 0.530f - 0.100f;
                            boxes = 5;
                            box_size = (0.330f - 0.216f) / (float)boxes;
                            tx = mx - shift - 0.216f;
                            box = (int)(tx / box_size);
                            xB = 0.216f + (box_size * box) + shift;
                            wB = box_size;
                            step = 1000000 * (int)Math.Pow(10, ((boxes - 1) - box));

                            string t = (shift >= 0.5f || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B ? m.VfoB : m.VfoA).ToString("F6", CultureInfo.InvariantCulture);
                            string[] parts = t.Split('.');
                            draw_box = ((boxes - 1) - box) <= parts[0].Length - 1;
                        }
                        //khz
                        if (((mx >= 0.346 && mx <= 0.416) && left) || ((mx >= 0.346 + shift && mx <= 0.416 + shift) && right))
                        {
                            yB = 0.100f;
                            hB = 0.530f - 0.100f;
                            boxes = 3;
                            box_size = (0.416f - 0.346f) / (float)boxes;
                            tx = mx - shift - 0.346f;
                            box = (int)(tx / box_size);
                            xB = 0.346f + (box_size * box) + shift;
                            wB = box_size;
                            step = 1000 * (int)Math.Pow(10, ((boxes - 1) - box));
                            draw_box = true;

                            // tune step, long mouse down, flip to different render state
                            if(vfo.MouseButtonDown && vfo.MouseDownLong)
                            {
                                if (left)
                                {
                                    button_state_vfoA = clsVfoDisplay.buttonState.NONE;
                                    vfo.VFOARenderState = clsVfoDisplay.renderState.TUNE_STEP;
                                    left = false;
                                }
                                if (right)
                                {
                                    button_state_vfoB = clsVfoDisplay.buttonState.NONE;
                                    vfo.VFOBRenderState = clsVfoDisplay.renderState.TUNE_STEP;
                                    right = false;
                                }
                            }
                        }
                    }
                    //small numbers
                    if (my >= 0.170 && my <= 0.530)
                    {
                        //hz
                        if (((mx >= 0.425 && mx <= 0.480) && left) || ((mx >= 0.425 + shift && mx <= 0.480 + shift) && right))
                        {
                            yB = 0.170f;
                            hB = 0.530f - 0.170f;

                            boxes = 3;
                            box_size = (0.480f - 0.425f) / (float)boxes;
                            tx = mx - shift - 0.425f;
                            box = (int)(tx / box_size);
                            xB = 0.425f + (box_size * box) + shift;
                            wB = box_size;
                            step = (int)Math.Pow(10, ((boxes - 1) - box));
                            draw_box = true;
                        }
                    }

                    vfo.MouseOverVfoB = shift != 0 || vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_B;

                    //plotText($"{mx.ToString("f3")},{my.ToString("f3")} -- boxes:{boxes} box:{box}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);
                }

                float old_h = h;
                if ((vfo.VFOARenderState != clsVfoDisplay.renderState.VFO && disp_a) ||
                    (vfo.VFOBRenderState != clsVfoDisplay.renderState.VFO && disp_b))
                {
                    //make full height for the drawBand/drawMode etc
                    h = rect.Height * (mi.Size.Height / m.YRatio);
                }

                float render_state_shift_vfo_b = vfo.VFODispMode == clsVfoDisplay.VFODisplayMode.VFO_BOTH ? /*0.510*/0.5f + m.PadX / 2f : 0f;
                if (vfo.VFOARenderState == clsVfoDisplay.renderState.BAND)
                {
                    button_state_vfoA = drawBand(x, y, w, h, rect, vfo, m, 0, x_multy);
                }
                if (vfo.VFOBRenderState == clsVfoDisplay.renderState.BAND)
                {
                    button_state_vfoB = drawBand(x, y, w, h, rect, vfo, m, render_state_shift_vfo_b, x_multy);
                }
                if (vfo.VFOARenderState == clsVfoDisplay.renderState.MODE)
                {
                    button_state_vfoA = drawMode(x, y, w, h, rect, vfo, m, 0, x_multy);
                }
                if (vfo.VFOBRenderState == clsVfoDisplay.renderState.MODE)
                {
                    button_state_vfoB = drawMode(x, y, w, h, rect, vfo, m, render_state_shift_vfo_b, x_multy);
                }
                if (vfo.VFOARenderState == clsVfoDisplay.renderState.FILTER)
                {
                    button_state_vfoA = drawFilter(x, y, w, h, rect, vfo, m, 0, x_multy);
                }
                if (vfo.VFOBRenderState == clsVfoDisplay.renderState.FILTER)
                {
                    button_state_vfoB = drawFilter(x, y, w, h, rect, vfo, m, render_state_shift_vfo_b, x_multy);
                }
                if (vfo.VFOARenderState == clsVfoDisplay.renderState.TUNE_STEP)
                {
                    button_state_vfoA = drawTuneStep(x, y, w, h, rect, vfo, m, 0, x_multy);
                }
                if (vfo.VFOBRenderState == clsVfoDisplay.renderState.TUNE_STEP)
                {
                    button_state_vfoB = drawTuneStep(x, y, w, h, rect, vfo, m, render_state_shift_vfo_b, x_multy);
                }
                //recover old h
                h = old_h;

                if (draw_box && mouse_over_good)
                {
                    vfo.AdjustStep = step;
                    if (vfo.MouseOverVfoB)
                        button_state_vfoB = clsVfoDisplay.buttonState.VFO;
                    else
                        button_state_vfoA = clsVfoDisplay.buttonState.VFO;
                }
                else
                {
                    vfo.AdjustStep = 0;
                }

                if (draw_box || button_back_box)
                {
                    SharpDX.RectangleF rctB = new SharpDX.RectangleF(x + (w * xB) * x_multy, y + (h * yB), w * wB * x_multy, h * hB);
                    _renderTarget.FillRectangle(rctB, getDXBrushForColour(vfo.DigitHighlightColour));
                }

                // set the button state
                if (mouse_over_good)
                {
                    if (vfo.MouseOverVfoB)
                        vfo.VFOBButtonState = button_state_vfoB;
                    else
                        vfo.VFOAButtonState = button_state_vfoA;
                }

                // nothing to do down below unless we are in VFO mode                
                disp_a = disp_a && vfo.VFOARenderState == clsVfoDisplay.renderState.VFO;
                disp_b = disp_b && vfo.VFOBRenderState == clsVfoDisplay.renderState.VFO;

                // plotting the normal display
                rect.Width *= x_multy;

                // frequency
                string MHz;
                string kHz;
                string hz;
                if (disp_a)
                {
                    plotText("VFO A", x + (w * 0.01f) * x_multy, y + (h * 0.03f), h, rect.Width, vfo.FontSize, vfo.TypeColour, nVfoAFade, vfo.FontFamily, vfo.Style);
                }

                // 0.50 difference in x between vfoa/b on both mode

                if (disp_b)
                {
                    if (m.RX == 1 && m.RX2Enabled && (m.MultiRxEnabled || m.Split) && m.VfoSub >= 0) //[2.10.3.6]MW0LGE added m.vfosub >= 0
                    {
                        // vfoa sub
                        plotText("VFO Sub", x + (w * 0.01f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.03f), h, rect.Width, vfo.FontSize, vfo.TypeColour, nVfoBFade, vfo.FontFamily, vfo.Style);
                    }
                    else
                        plotText("VFO B", x + (w * 0.01f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.03f), h, rect.Width, vfo.FontSize, vfo.TypeColour, nVfoBFade, vfo.FontFamily, vfo.Style);
                }

                if (disp_a) 
                { 
                    getParts(m.VfoA, out MHz, out kHz, out hz);
                    string sVfoA = MHz + "." + kHz;// + "." + hz;
                    plotText(sVfoA, x + (w * 0.415f) * x_multy, y + (h * 0.02f), h, rect.Width, vfo.FontSize * 1.5f, vfo.FrequencyColour, nVfoAFade, vfo.FontFamily, vfo.Style, true);
                    plotText(hz, x + (w * 0.48f) * x_multy, y + (h * 0.11f), h, rect.Width, vfo.FontSize * 1.2f, vfo.FrequencyColourSmall, nVfoAFade, vfo.FontFamily, vfo.Style, true);
                }

                double tmpVfoB;
                Band tmpVfoBBand;
                DSPMode tmpVfoBMode;
                string tmpVfoBFilterName;

                if (m.RX == 1 && m.RX2Enabled && (m.MultiRxEnabled || m.Split))
                {
                    tmpVfoB = m.VfoSub;
                    tmpVfoBBand = m.BandVfoASub;
                    tmpVfoBMode = m.ModeVfoA;
                    tmpVfoBFilterName = m.FilterVfoAName;
                }
                else
                {
                    tmpVfoB = m.VfoB;
                    tmpVfoBBand = m.BandVfoB;
                    tmpVfoBMode = m.ModeVfoB;
                    tmpVfoBFilterName = m.FilterVfoBName;
                }

                if (disp_b)
                {
                    // hz VFOB
                    getParts(tmpVfoB, out MHz, out kHz, out hz);
                    string sVfoB = MHz + "." + kHz;// + "." + hz;
                    plotText(sVfoB, x + (w * 0.415f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.02f), h, rect.Width, vfo.FontSize * 1.5f, vfo.FrequencyColour, nVfoBFade, vfo.FontFamily, vfo.Style, true);
                    plotText(hz, x + (w * 0.48f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.11f), h, rect.Width, vfo.FontSize * 1.2f, vfo.FrequencyColourSmall, nVfoBFade, vfo.FontFamily, vfo.Style, true);
                }

                if (disp_a)
                {
                    // mode VFOA
                    plotText(m.ModeVfoA.ToString(), x + (w * 0.01f) * x_multy, y + (h * 0.52f), h, rect.Width, vfo.FontSize * 1f, vfo.ModeColour, nVfoAFade, vfo.FontFamily, vfo.Style);
                }

                if (disp_b)
                {
                    // mode VFOB
                    plotText(tmpVfoBMode.ToString(), x + (w * 0.01f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.52f), h, rect.Width, vfo.FontSize * 1f, vfo.ModeColour, nVfoBFade, vfo.FontFamily, vfo.Style);
                }

                if (disp_a)
                {
                    //band VFOA
                    string sBand = BandStackManager.BandToString(m.BandVfoA);
                    if (sBand.EndsWith("M")) sBand = sBand.ToLower();
                    plotText(sBand + " band", x + (w * 0.48f) * x_multy, y + (h * 0.54f), h, rect.Width, vfo.FontSize * 1f, vfo.BandColour, nVfoAFade, vfo.FontFamily, vfo.Style, true);
                }

                if (disp_b)
                {
                    //band VFOB
                    string sBand = BandStackManager.BandToString(tmpVfoBBand);
                    if (sBand.EndsWith("M")) sBand = sBand.ToLower();
                    plotText(sBand + " band", x + (w * 0.48f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.54f), h, rect.Width, vfo.FontSize * 1f, vfo.BandColour, nVfoBFade, vfo.FontFamily, vfo.Style, true);
                }
                
                if (disp_a)
                {
                    //split only on VFOA
                    SharpDX.RectangleF rectSplit = new SharpDX.RectangleF(x + (w * 0.1f) * x_multy, y + (h * 0.03f), w * 0.1f * x_multy, h * 0.4f);
                    if(!(button_back_box && button_state_vfoA == clsVfoDisplay.buttonState.SPLIT)) _renderTarget.FillRectangle(rectSplit, getDXBrushForColour(vfo.SplitBackColour, nVfoAFade));
                    System.Drawing.Color splitColor = m.Split ? vfo.SplitColour : System.Drawing.Color.Black;
                    plotText(m.QuickSplitEnabled ? "QSPLT" : "SPLIT", rectSplit.X + (w * (m.QuickSplitEnabled ? 0.01f : 0.015f)) * x_multy, rectSplit.Y, h, rect.Width, vfo.FontSize * 1f, splitColor, nVfoAFade, vfo.FontFamily, vfo.Style);
                }

                //-- tx/rx state
                bool bCanVfoATx = m.RX == 1 && !m.TXVFOb && !m.Split;
                bool bCanVfoBTx = ((m.TXVFOb && m.RX == 1 && !m.RX2Enabled) || (m.TXVFOb && m.RX == 2 && m.RX2Enabled) || (m.Split && m.RX == 1));

                bool bRxVFOA = !m.MOX && m.RX == 1;
                bool bTxVFOA = m.MOX && bCanVfoATx;

                bool bRxVFOB = !m.MOX && (m.MultiRxEnabled || (m.RX2Enabled && m.RX == 2));
                bool bTxVFOB = m.MOX && bCanVfoBTx;

                // tx/rx box colours
                System.Drawing.Color cRx = vfo.RxColour;
                System.Drawing.Color cDimRx = System.Drawing.Color.FromArgb((int)(cRx.R * 0.6f), (int)(cRx.G * 0.6f), (int)(cRx.B * 0.6f));
                System.Drawing.Color cDimerRx = System.Drawing.Color.FromArgb((int)(cRx.R * 0.3f), (int)(cRx.G * 0.3f), (int)(cRx.B * 0.3f));
                System.Drawing.Color cTx = vfo.TxColour;
                System.Drawing.Color cDimTx = System.Drawing.Color.FromArgb((int)(cTx.R * 0.6f), (int)(cTx.G * 0.6f), (int)(cTx.B * 0.6f));
                System.Drawing.Color cDimerTx = System.Drawing.Color.FromArgb((int)(cTx.R * 0.3f), (int)(cTx.G * 0.3f), (int)(cTx.B * 0.3f));                
                System.Drawing.Color boxColour;
                System.Drawing.Color txtColour;

                if (disp_a)
                {
                    //rx box VFOA
                    rct = new SharpDX.RectangleF(x + (w * 0.1f) * x_multy, y + (h * 0.52f), w * 0.048f * x_multy, h * 0.4f);
                    boxColour = bRxVFOA ? cDimRx : cDimerRx;
                    _renderTarget.FillRectangle(rct, getDXBrushForColour(boxColour, nVfoAFade));
                    txtColour = bRxVFOA ? cRx : System.Drawing.Color.Black;
                    plotText("RX", rct.X + (w * 0.005f) * x_multy, rct.Y, h, rect.Width, vfo.FontSize * 1f, txtColour, nVfoAFade, vfo.FontFamily, vfo.Style);

                    //tx box VFOA
                    rct = new SharpDX.RectangleF(x + (w * 0.152f) * x_multy, y + (h * 0.52f), w * 0.048f * x_multy, h * 0.4f);
                    boxColour = bCanVfoATx ? cDimTx : cDimerTx;
                    if (!(button_back_box && button_state_vfoA == clsVfoDisplay.buttonState.TX)) _renderTarget.FillRectangle(rct, getDXBrushForColour(boxColour, nVfoAFade));
                    txtColour = bTxVFOA ? cTx : System.Drawing.Color.Black;
                    plotText("TX", rct.X + (w * 0.005f) * x_multy, rct.Y, h, rect.Width, vfo.FontSize * 1f, txtColour, nVfoAFade, vfo.FontFamily, vfo.Style);
                }
                if (disp_b)
                {
                    //rx box VFOB
                    rct = new SharpDX.RectangleF(x + (w * 0.1f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.52f), w * 0.048f * x_multy, h * 0.4f);
                    boxColour = bRxVFOB ? cDimRx : cDimerRx;
                    _renderTarget.FillRectangle(rct, getDXBrushForColour(boxColour, nVfoBFade));
                    txtColour = bRxVFOB ? cRx : System.Drawing.Color.Black;
                    plotText("RX", rct.X + (w * 0.005f) * x_multy, rct.Y, h, rect.Width, vfo.FontSize * 1f, txtColour, nVfoBFade, vfo.FontFamily, vfo.Style);

                    //tx box VFOB
                    rct = new SharpDX.RectangleF(x + (w * 0.152f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.52f), w * 0.048f * x_multy, h * 0.4f);
                    boxColour = bCanVfoBTx ? cDimTx : cDimerTx;
                    if(!(button_back_box && button_state_vfoB == clsVfoDisplay.buttonState.TX)) _renderTarget.FillRectangle(rct, getDXBrushForColour(boxColour, nVfoBFade));
                    txtColour = bTxVFOB ? cTx : System.Drawing.Color.Black;
                    plotText("TX", rct.X + (w * 0.005f) * x_multy, rct.Y, h, rect.Width, vfo.FontSize * 1f, txtColour, nVfoBFade, vfo.FontFamily, vfo.Style);
                }

                if (disp_a)
                {
                    //filter VFOA
                    rct = new SharpDX.RectangleF(x + (w * 0.25f) * x_multy, y + (h * 0.54f), w * 0.048f, h * 0.4f);
                    plotText(m.FilterVfoAName, rct.X + (w * 0.005f) * x_multy, rct.Y, h, rect.Width, vfo.FontSize * 1f, vfo.FilterColour, nVfoAFade, vfo.FontFamily, vfo.Style);
                }
                if (disp_b)
                {
                    //filter VFOB
                    rct = new SharpDX.RectangleF(x + (w * 0.25f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.54f), w * 0.048f * x_multy, h * 0.4f);
                    plotText(tmpVfoBFilterName, rct.X + (w * 0.005f) * x_multy, rct.Y, h, rect.Width, vfo.FontSize * 1f, vfo.FilterColour, nVfoBFade, vfo.FontFamily, vfo.Style);
                }

                if (vfo.ShowBandText)
                {
                    // band text, needs the full height, as we adjusted it above
                    float bt_h = rect.Height * (mi.Size.Height / m.YRatio);

                    if (disp_a)
                    {
                        rct = new SharpDX.RectangleF(x + (w * 0.250f) * x_multy, y + (bt_h * 0.85f), w * 0.08f, bt_h * 0.03f);
                        plotText(m.VFOABandText, rct.X, rct.Y, bt_h, rect.Width, vfo.FontSize * 1f, vfo.BandTextColour, nVfoAFade, vfo.FontFamily, vfo.Style, false, true);
                    }
                    if (disp_b)
                    {
                        rct = new SharpDX.RectangleF(x + (w * 0.250f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (bt_h * 0.85f), w * 0.08f, bt_h * 0.03f);
                        plotText(m.VFOBBandText, rct.X, rct.Y, bt_h, rect.Width, vfo.FontSize * 1f, vfo.BandTextColour, nVfoBFade, vfo.FontFamily, vfo.Style, false, true);
                    }
                }

                // convert the cache bitmap to the directX version if required
                convertImageToDX("vfo_lock", true);
                convertImageToDX("vfo_sync", true);

                //vfo lock / vfo sync icons
                Matrix3x2 originalTransform = _renderTarget.Transform;
                AntialiasMode originalAM = _renderTarget.AntialiasMode;
                _renderTarget.AntialiasMode = AntialiasMode.Aliased;
                _renderTarget.Transform = Matrix3x2.Identity;
                if (disp_a)
                {                    
                    if (_images.ContainsKey("vfo_lock") && _bitmap_brushes.ContainsKey("vfo_lock"))
                    {
                        rct = new SharpDX.RectangleF(x + (w * 0.199f) * x_multy, y + (h * 0.58f), w * 0.026f * x_multy, w * 0.026f * x_multy);
                        SharpDX.Direct2D1.Bitmap b = _images["vfo_lock"];
                        _renderTarget.FillRectangle(rct, _bitmap_brushes["vfo_lock"]);
                        _renderTarget.FillOpacityMask(b, getDXBrushForColour(vfo.LockColour, m.VFOALock ? nVfoAFade : Math.Min(64, nVfoAFade)), OpacityMaskContent.Graphics, rct, new RawRectangleF(0, 0, b.Size.Width, b.Size.Height));
                    }
                    if (_images.ContainsKey("vfo_sync") && _bitmap_brushes.ContainsKey("vfo_sync"))
                    {
                        rct = new SharpDX.RectangleF(x + (w * 0.224f) * x_multy, y + (h * 0.58f), w * 0.026f * x_multy, w * 0.026f * x_multy);
                        SharpDX.Direct2D1.Bitmap b = _images["vfo_sync"];
                        _renderTarget.FillRectangle(rct, _bitmap_brushes["vfo_sync"]);
                        _renderTarget.FillOpacityMask(b, getDXBrushForColour(vfo.SyncColour, m.VFOSync ? nVfoAFade : Math.Min(64, nVfoAFade)), OpacityMaskContent.Graphics, rct, new RawRectangleF(0, 0, b.Size.Width, b.Size.Height));
                    }
                }
                if (disp_b)
                {
                    if (_images.ContainsKey("vfo_lock") && _bitmap_brushes.ContainsKey("vfo_lock"))
                    {
                        rct = new SharpDX.RectangleF(x + (w * 0.199f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.58f), w * 0.026f * x_multy, w * 0.026f * x_multy);
                        SharpDX.Direct2D1.Bitmap b = _images["vfo_lock"];
                        _renderTarget.FillRectangle(rct, _bitmap_brushes["vfo_lock"]);
                        _renderTarget.FillOpacityMask(b, getDXBrushForColour(vfo.LockColour, m.VFOBLock ? nVfoBFade : Math.Min(64, nVfoBFade)), OpacityMaskContent.Graphics, rct, new RawRectangleF(0, 0, b.Size.Width, b.Size.Height));
                    }
                    if (_images.ContainsKey("vfo_sync") && _bitmap_brushes.ContainsKey("vfo_sync"))
                    {
                        rct = new SharpDX.RectangleF(x + (w * 0.224f) * x_multy + (w * (0.50f - x_shift)) * x_multy, y + (h * 0.58f), w * 0.026f * x_multy, w * 0.026f * x_multy);
                        SharpDX.Direct2D1.Bitmap b = _images["vfo_sync"];
                        _renderTarget.FillRectangle(rct, _bitmap_brushes["vfo_sync"]);
                        _renderTarget.FillOpacityMask(b, getDXBrushForColour(vfo.SyncColour, m.VFOSync ? nVfoBFade : Math.Min(64, nVfoBFade)), OpacityMaskContent.Graphics, rct, new RawRectangleF(0, 0, b.Size.Width, b.Size.Height));
                    }
                }
                _renderTarget.AntialiasMode = originalAM;
                _renderTarget.Transform = originalTransform;

                //if (vfo.MouseEntered)
                //{
                //    plotText($"{mx.ToString("f3")},{my.ToString("f3")}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);
                //}

                //plotText($"a render state = {vfo.VFOARenderState}", x, y, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);
                //plotText($"b render state = {vfo.VFOBRenderState}", x, y + 20, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);

                //plotText($"a button state = {vfo.VFOAButtonState}", x, y + 40, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);
                //plotText($"b button state = {vfo.VFOBButtonState}", x, y + 60, h, rect.Width, 12, System.Drawing.Color.White, 255, vfo.FontFamily, vfo.Style);
            }
            private void renderClock(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsClock clk = (clsClock)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                DateTime now = DateTime.Now;
                DateTime UTCnow = DateTime.UtcNow;

                string sUtc;
                string sUtcAmPm = "";
                float fPadUtc = 0;
                if (clk.Show24HourCLock)
                    sUtc = UTCnow.ToString("HH:mm:ss");
                else
                {
                    sUtc = UTCnow.ToString("h:mm:ss");
                    sUtcAmPm = UTCnow.Hour >= 12 ? "pm" : "am";
                    if (sUtc.Length == 7) fPadUtc = w * 0.03f;
                }

                string sLoc;
                string sLocAmPm = "";
                float fPadLoc = 0;
                if (clk.Show24HourCLock)
                    sLoc = now.ToString("HH:mm:ss");
                else
                {
                    sLoc = now.ToString("h:mm:ss");
                    sLocAmPm = now.Hour >= 12 ? "pm" : "am";
                    if (sLoc.Length == 7) fPadLoc = w * 0.03f;
                }

                string sUtcDate = UTCnow.ToString("ddd d MMM yyyy");
                string sLocDate = now.ToString("ddd d MMM yyyy");

                if (clk.ShowType)
                {
                    plotText("local", x + (w * 0.01f), y + (h * 0.03f), h, rect.Width, clk.FontSize, clk.TypeTitleColour, 255, clk.FontFamily, clk.Style);
                    plotText("utc", x + (w * 0.52f), y + (h * 0.03f), h, rect.Width, clk.FontSize, clk.TypeTitleColour, 255, clk.FontFamily, clk.Style);
                }

                SharpDX.RectangleF rct;

                //time
                rct = new SharpDX.RectangleF(x + (w * 0.12f), y + (h * 0.02f), w, h);
                plotText(sLoc, rct.X + fPadLoc, rct.Y, h, rect.Width, clk.FontSize * 1.9f, clk.TimeColour, 255, clk.FontFamily, clk.Style);
                if (!clk.Show24HourCLock)
                    plotText(sLocAmPm, rct.X + (w * 0.228f), rct.Y + (h * 0.285f), h, rect.Width, clk.FontSize * 0.8f, clk.TimeColour, 255, clk.FontFamily, clk.Style);

                rct = new SharpDX.RectangleF(x + (w * 0.12f) + (w * 0.52f), y + (h * 0.02f), w, h);
                plotText(sUtc, rct.X + fPadUtc, rct.Y, h, rect.Width, clk.FontSize * 1.9f, clk.TimeColour, 255, clk.FontFamily, clk.Style);
                if (!clk.Show24HourCLock)
                    plotText(sUtcAmPm, rct.X + (w * 0.228f), rct.Y + (h * 0.285f), h, rect.Width, clk.FontSize * 0.8f, clk.TimeColour, 255, clk.FontFamily, clk.Style);

                //date
                rct = new SharpDX.RectangleF(x + (w * 0.132f), y + (h * 0.6f), w, h);
                plotText(sLocDate, rct.X, rct.Y, h, rect.Width, clk.FontSize * 0.9f, clk.DateColour, 255, clk.FontFamily, clk.Style);
                rct = new SharpDX.RectangleF(x + (w * 0.132f) + (w * 0.52f), y + (h * 0.6f), w, h);
                plotText(sUtcDate, rct.X, rct.Y, h, rect.Width, clk.FontSize * 0.9f, clk.DateColour, 255, clk.FontFamily, clk.Style);
            }
            private void renderSignalTextDisplay(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsSignalText st = (clsSignalText)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                float fontSizeEmScaled;
                SizeF szTextSize;

                // s-reading
                fontSizeEmScaled = (st.FontSize / 16f) * (w / 52f);
                Common.SMeterFromDBM2(st.Value, MeterManager.IsAboveS9Frequency(_rx), out int S, out int dBmOver);
                string sText = "S " + S.ToString();
                szTextSize = measureString(sText, st.FontFamily, st.FntStyle, fontSizeEmScaled);
                SharpDX.RectangleF txtrect = new SharpDX.RectangleF(x + (w * 0.5f) - (szTextSize.Width * 0.5f), y, szTextSize.Width, szTextSize.Height);
                _renderTarget.DrawText(sText, getDXTextFormatForFont(st.FontFamily, fontSizeEmScaled, st.FntStyle), txtrect, getDXBrushForColour(st.FontColour, 255));
                if (dBmOver > 0)
                {
                    plotText("+" + dBmOver.ToString(), txtrect.X + txtrect.Width + (w * 0.005f), txtrect.Y + (h * 0.24f), h, rect.Width, st.FontSize * 0.6f, st.FontColour, 255, st.FontFamily, st.FntStyle);
                }

                if (st.ShowSubMarker)
                {
                    //uv reading
                    sText = Common.UVfromDBM(st.Value).ToString("f2") + "uV";
                    plotText(sText, x + (w * 0.98f), y + (h * 0.22f), h, rect.Width, st.FontSize * 0.55f, st.HistoryColour, 255, st.FontFamily, st.FntStyle, true);

                    //dbm reading
                    sText = st.Value.ToString("f1") + MeterManager.ReadingUnits(st.ReadingSource);
                    plotText(sText, x + (w * 0.02f), y + (h * 0.22f), h, rect.Width, st.FontSize * 0.55f, st.HistoryColour, 255, st.FontFamily, st.FntStyle);
                }

                if (st.ShowPeakValue)
                {
                    //peaks
                    fontSizeEmScaled = ((st.FontSize * 0.5f) / 16f) * (w / 52f);
                    Common.SMeterFromDBM2(st.MaxHistory, MeterManager.IsAboveS9Frequency(_rx), out S, out dBmOver);
                    sText = "S " + S.ToString();
                    szTextSize = measureString(sText, st.FontFamily, st.FntStyle, fontSizeEmScaled);
                    txtrect = new SharpDX.RectangleF(x + (w * 0.5f) - (szTextSize.Width * 0.5f), y + (h * 0.62f), szTextSize.Width, szTextSize.Height);
                    _renderTarget.DrawText(sText, getDXTextFormatForFont(st.FontFamily, fontSizeEmScaled, st.FntStyle), txtrect, getDXBrushForColour(st.PeakValueColour, 255));
                    if (dBmOver > 0)
                    {
                        plotText("+" + dBmOver.ToString(), txtrect.X + txtrect.Width + (w * 0.005f), txtrect.Y + (h * 0.12f), h, rect.Width, st.FontSize * 0.3f, st.PeakValueColour, 255, st.FontFamily, st.FntStyle);
                    }

                    if (st.ShowSubMarker)
                    {
                        //uv peak
                        sText = Common.UVfromDBM(st.MaxHistory).ToString("f2") + "uV";
                        plotText(sText, x + (w * 0.98f), y + (h * 0.65f), h, rect.Width, st.FontSize * 0.35f, st.PeakValueColour, 255, st.FontFamily, st.FntStyle, true);

                        //dbm peak
                        sText = st.MaxHistory.ToString("f1") + MeterManager.ReadingUnits(st.ReadingSource);
                        plotText(sText, x + (w * 0.02f), y + (h * 0.65f), h, rect.Width, st.FontSize * 0.35f, st.PeakValueColour, 255, st.FontFamily, st.FntStyle);
                    }
                }
            }
            //private void renderSpectrum(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            //{
            //    lock (_spectrumArrayLock)
            //    {
            //        if (_newSpectrumPassband == null || _currentSpectrumPassband == null) return;

            //        clsSpectrum spect = (clsSpectrum)mi;

            //        float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
            //        float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
            //        float w = rect.Width * (mi.Size.Width / m.XRatio);
            //        float h = rect.Height * (mi.Size.Height / m.YRatio);

            //        int nFade = 255;

            //        float top = Display.SpectrumGridMax; //dbm
            //        float bottom = Display.SpectrumGridMin; //-150; //dbm

            //        float yRange = top - bottom;
            //        float dbmToPixel = h / yRange;

            //        if (_spectrumReady)
            //        {
            //            unsafe
            //            {
            //                // copy to current
            //                fixed (void* srcptr = &_newSpectrumPassband[0])
            //                fixed (void* destptr = &_currentSpectrumPassband[0])
            //                    Win32.memcpy(destptr, srcptr, _currentSpectrumPassband.Length * sizeof(float));

            //                //for (int i = 0; i < _newSpectrumPassband.Length; i++)
            //                //{
            //                //    _currentSpectrumPassband[i] = (_newSpectrumPassband[i] * 0.3f) + (_currentSpectrumPassband[i] * 0.7f);
            //                //}
            //            }
            //            _spectrumReady = false;
            //        }

            //        if (_currentSpectrumPassband != null)
            //        {
            //            SharpDX.RectangleF rct = new SharpDX.RectangleF(x, y, w, h);
            //            _renderTarget.PushAxisAlignedClip(rct, AntialiasMode.Aliased);

            //            float xPos = x;
            //            float increment = w / _currentSpectrumPassband.Length;
            //            Vector2 oldPoint = new Vector2();
            //            Vector2 newPoint = new Vector2();

            //            oldPoint.X = x;
            //            oldPoint.Y = y + ((top - (float)_currentSpectrumPassband[0]) * dbmToPixel);

            //            SharpDX.Direct2D1.Brush b = getDXBrushForColour(System.Drawing.Color.White, nFade);

            //            for (int i = 0; i < _currentSpectrumPassband.Length; i++)
            //            {
            //                float yPos = y + ((top - (float)_currentSpectrumPassband[i]) * dbmToPixel);

            //                newPoint.X = xPos;
            //                newPoint.Y = yPos;

            //                _renderTarget.DrawLine(oldPoint, newPoint, b);

            //                oldPoint.X = newPoint.X;
            //                oldPoint.Y = newPoint.Y;

            //                xPos += increment;
            //            }

            //            _renderTarget.PopAxisAlignedClip();
            //        }
            //    }
            //}
            private void renderImage(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsImage img = (clsImage)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                string sImage = img.ImageName;
                if (string.IsNullOrEmpty(sImage)) return;

                string sKey = sImage + (img.DarkMode ? "-dark" : "");
                if (MeterManager.ContainsBitmap(sKey)) sImage = sKey;

                float fDiag = (float)Math.Sqrt((w * w) + (h * h));
                if(fDiag <= 450)
                    sKey = sImage + "-small";
                else if(fDiag >= 1200)
                    sKey = sImage + "-large";

                if (MeterManager.ContainsBitmap(sKey)) sImage = sKey; // with size

                if (MeterManager.ContainsBitmap(sImage))
                {
                    SharpDX.RectangleF imgRect = new SharpDX.RectangleF(x, y, w, h);

                    if (!img.Clipped)
                    {
                        _renderTarget.PushAxisAlignedClip(imgRect, AntialiasMode.Aliased); // prevent anything drawing from outside the rectangle, no need to cut the image
                    }
                    else
                    {
                        float cx = (img.ClipTopLeft.X / m.XRatio) * rect.Width;
                        float cy = (img.ClipTopLeft.Y / m.YRatio) * rect.Height;
                        float cw = rect.Width * (img.ClipSize.Width / m.XRatio);
                        float ch = rect.Height * (img.ClipSize.Height / m.YRatio);

                        SharpDX.RectangleF clipRect = new SharpDX.RectangleF(cx, cy, cw, ch);
                        _renderTarget.PushAxisAlignedClip(clipRect, AntialiasMode.Aliased); // prevent anything drawing from outside the rectangle, no nee to cut the image
                    }

                    // convert the cache bitmap to the directX version if required
                    convertImageToDX(sImage);

                    if (_images.ContainsKey(sImage))
                    {
                        Ellipse ellipse;
                        Layer layer = null;
                        EllipseGeometry ellipseGeometry = null;

                        if (img.ClippedEllipse)
                        {
                            try
                            {
                                ellipse = new Ellipse(new RawVector2(x + (img.ClipEllipseCentre.X * w), y + (img.ClipEllipseCentre.Y * h)), img.ClipEllipseRadius.Width * w, img.ClipEllipseRadius.Height * h);
                                ellipseGeometry = new EllipseGeometry(_renderTarget.Factory, ellipse);
                                Geometry[] geometries = new Geometry[] { ellipseGeometry };
                                layer = new Layer(_renderTarget);
                                LayerParameters layerParameters = new LayerParameters
                                {
                                    //ContentBounds = new RawRectangleF(float.NegativeInfinity, float.NegativeInfinity, float.PositiveInfinity, float.PositiveInfinity),
                                    ContentBounds = imgRect,
                                    GeometricMask = ellipseGeometry,
                                    MaskAntialiasMode = AntialiasMode.PerPrimitive,
                                    Opacity = 1.0f,
                                    OpacityBrush = null,
                                    LayerOptions = LayerOptions.None,
                                    MaskTransform = _renderTarget.Transform
                                };
                                _renderTarget.PushLayer(ref layerParameters, layer);
                            }
                            catch { }
                        }

                        SharpDX.Direct2D1.Bitmap b = _images[sImage];

                        // maintain aspect ratio, the clip removes anything outside the rect
                        float im_w = b.Size.Width;
                        float im_h = b.Size.Height;

                        if (w > h)
                            imgRect.Height = imgRect.Width * (im_h / im_w);
                        else
                            imgRect.Width = imgRect.Height * (im_w / im_h);

                        _renderTarget.DrawBitmap(b, imgRect, 1f, BitmapInterpolationMode.Linear);//, sourceRect);

                        if (img.ClippedEllipse)
                        {
                            try
                            {
                                _renderTarget.PopLayer();
                            }
                            catch { }
                            try
                            {
                                Utilities.Dispose(ref ellipseGeometry);
                            }
                            catch { }
                            try
                            {
                                Utilities.Dispose(ref layer);
                            }
                            catch { }
                        }
                    }

                    _renderTarget.PopAxisAlignedClip();
                }
            }
            private void renderNeedle(SharpDX.RectangleF rect, clsMeterItem mi, clsMeter m)
            {
                clsNeedleItem ni = (clsNeedleItem)mi;

                float x = (mi.DisplayTopLeft.X / m.XRatio) * rect.Width;
                float y = (mi.DisplayTopLeft.Y / m.YRatio) * rect.Height;
                float w = rect.Width * (mi.Size.Width / m.XRatio);
                float h = rect.Height * (mi.Size.Height / m.YRatio);

                SharpDX.RectangleF nirect = new SharpDX.RectangleF(x, y, w, h);
                //_renderTarget.DrawRectangle(nirect, getDXBrushForColour(System.Drawing.Color.Red));

                _renderTarget.PushAxisAlignedClip(nirect, AntialiasMode.Aliased); // prevent anything drawing from outside the rectangle

                // needle offset from centre
                float cX = x + (w / 2);
                float cY = y + (h / 2);
                float startX = cX + (w * ni.NeedleOffset.X);
                float startY = cY + (h * ni.NeedleOffset.Y);

                float rotation = 180f;

                float radiusX = (w / 2) * (ni.LengthFactor * ni.RadiusRatio.X);
                float radiusY = (w / 2) * (ni.LengthFactor * ni.RadiusRatio.Y);

                //todo
                switch (ni.Placement)
                {
                    case clsNeedleItem.NeedlePlacement.Bottom:
                        rotation = 180f;
                        break;
                    case clsNeedleItem.NeedlePlacement.Left:
                        //rotation = 90f;
                        break;
                    case clsNeedleItem.NeedlePlacement.Top:
                        //rotation = 0f;
                        break;
                    case clsNeedleItem.NeedlePlacement.Right:
                        //rotation = 270f;
                        break;
                }

                float eX, eY, dX, dY;
                float endMaxX = 0 , endMaxY = 0;

                if (ni.ShowHistory || ni.Setup || ni.PeakHold)
                {
                    float valueMin;
                    float valueMax;

                    if (ni.Setup) 
                    {
                        KeyValuePair<float, PointF> kvp;
                        kvp = ni.ScaleCalibration.OrderBy(p => p.Key).First();
                        valueMin = kvp.Key;
                        kvp = ni.ScaleCalibration.OrderByDescending(p => p.Key).First();
                        valueMax = kvp.Key;
                    }
                    else
                    {
                        valueMin = ni.MinHistory;
                        valueMax = ni.MaxHistory;
                    }

                    getPerc(ni, valueMin, out float minPercX, out float minPercY, out PointF minHistoryMin, out PointF minHistoryMax);
                    getPerc(ni, valueMax, out float maxPercX, out float maxPercY, out PointF maxHistoryMin, out PointF maxHistoryMax);

                    // map the meter scales to pixels
                    float eXmin = x + (minHistoryMin.X * w) + (minPercX * ((minHistoryMax.X - minHistoryMin.X) * w));
                    float eYmin = y + (minHistoryMin.Y * h) + (minPercY * ((minHistoryMax.Y - minHistoryMin.Y) * h));
                    float eXmax = x + (maxHistoryMin.X * w) + (maxPercX * ((maxHistoryMax.X - maxHistoryMin.X) * w));
                    float eYmax = y + (maxHistoryMin.Y * h) + (maxPercY * ((maxHistoryMax.Y - maxHistoryMin.Y) * h));

                    // calc angle required
                    float dXmin = startX - eXmin;
                    float dYmin = startY - eYmin;
                    float dXmax = startX - eXmax;
                    float dYmax = startY - eYmax;
                    // expand for ellipse
                    dXmin /= ni.RadiusRatio.X;
                    dYmin /= ni.RadiusRatio.Y;
                    dXmax /= ni.RadiusRatio.X;
                    dYmax /= ni.RadiusRatio.Y;
                    //
                    float angMin = (float)Math.Atan2(dYmin, dXmin);
                    float angMax = (float)Math.Atan2(dYmax, dXmax);

                    float endMinX = startX + (float)(Math.Cos(angMin + degToRad(rotation)) * radiusX);
                    float endMinY = startY + (float)(Math.Sin(angMin + degToRad(rotation)) * radiusY);
                    endMaxX = startX + (float)(Math.Cos(angMax + degToRad(rotation)) * radiusX);
                    endMaxY = startY + (float)(Math.Sin(angMax + degToRad(rotation)) * radiusY);

                    if (ni.ShowHistory || ni.Setup)
                    {
                        PathGeometry sharpGeometry = new PathGeometry(_renderTarget.Factory);

                        GeometrySink geo = sharpGeometry.Open();
                        geo.BeginFigure(new SharpDX.Vector2(startX, startY), FigureBegin.Filled);

                        geo.AddLine(new SharpDX.Vector2(endMinX, endMinY));

                        ArcSegment arcSegment = new ArcSegment();
                        arcSegment.Point = new SharpDX.Vector2(endMaxX, endMaxY);
                        arcSegment.SweepDirection = ni.Direction == clsNeedleItem.NeedleDirection.Clockwise ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
                        arcSegment.ArcSize = Math.Abs(radToDeg(angMax) - radToDeg(angMin)) <= 180f ? ArcSize.Small : ArcSize.Large;
                        arcSegment.Size = new Size2F(radiusX, radiusY);
                        geo.AddArc(arcSegment);

                        geo.EndFigure(FigureEnd.Closed); // adds the closing line
                        geo.Close();

                        _renderTarget.FillGeometry(sharpGeometry, getDXBrushForColour(ni.HistoryColour, ni.HistoryColour.A));

                        Utilities.Dispose(ref geo);
                        geo = null;
                        Utilities.Dispose(ref sharpGeometry);
                        sharpGeometry = null;
                    }
                }

                getPerc(ni, ni.Value, out float percX, out float percY, out PointF min, out PointF max);

                // map the meter scales to pixels
                eX = x + (min.X * w) + (percX * ((max.X - min.X) * w));
                eY = y + (min.Y * h) + (percY * ((max.Y - min.Y) * h));

                // calc angle required
                dX = startX - eX;
                dY = startY - eY;
                // expand
                dX /= ni.RadiusRatio.X;
                dY /= ni.RadiusRatio.Y;
                float ang = (float)Math.Atan2(dY, dX);

                float endX = startX + (float)(Math.Cos(ang + degToRad(rotation)) * radiusX);
                float endY = startY + (float)(Math.Sin(ang + degToRad(rotation)) * radiusY);

                float fScale = 1f;
                if (ni.ScaleStrokeWidth)
                {
                    float fDiag = (float)Math.Sqrt((w*w) + (h*h));
                    fScale = fDiag / 450f;
                }
                float fStrokeWidth = ni.StrokeWidth * fScale;

                if (ni.PeakHold)
                {
                    if (ni.Shadow)
                    {
                        float fDeltaFromCentre = -((w / 2f) - (endMaxX - x));
                        float fR = fDeltaFromCentre / (w / 2f);
                        float fShift = (fStrokeWidth * 1.5f) * fR;
                        float fTotalWidth = fStrokeWidth * 3f;
                        float tmpStartX = startX + fShift;
                        float tmpStartY = startY + (fStrokeWidth * 1.5f);
                        float tmpEndX = endMaxX + fShift;
                        float tmpEndY = endMaxY + (fStrokeWidth * 1.5f);

                        for (int n = 0; n < 8; n++)
                        {
                            float fReduce = (n / 7f) * fTotalWidth;
                            _renderTarget.DrawLine(new SharpDX.Vector2(tmpStartX, tmpStartY), new SharpDX.Vector2(tmpEndX, tmpEndY), getDXBrushForColour(System.Drawing.Color.Black, (int)ni.PeakNeedleShadowFade), fTotalWidth - fReduce);
                        }

                        if (Math.Abs(endX - endMaxX) > fStrokeWidth) 
                            ni.PeakNeedleShadowFade += 1;
                        else
                            ni.PeakNeedleShadowFade -= 1;
                    }
                    _renderTarget.DrawLine(new SharpDX.Vector2(startX, startY), new SharpDX.Vector2(endMaxX, endMaxY), getDXBrushForColour(ni.PeakHoldMarkerColour, 255), fStrokeWidth);
                }

                //shadow?
                if (ni.Shadow)
                {
                    float fDeltaFromCentre = -((w / 2f) - (endX - x));
                    float fR = fDeltaFromCentre / (w / 2f);
                    float fShift = (fStrokeWidth * 1.5f) * fR;
                    float fTotalWidth = fStrokeWidth * 3f;
                    float tmpStartX = startX + fShift;
                    float tmpStartY = startY + (fStrokeWidth * 1.5f);
                    float tmpEndX = endX + fShift;
                    float tmpEndY = endY + (fStrokeWidth * 1.5f);

                    for (int n = 0; n<8; n++) 
                    {
                        float fReduce = (n / 7f) * fTotalWidth;
                        _renderTarget.DrawLine(new SharpDX.Vector2(tmpStartX, tmpStartY), new SharpDX.Vector2(tmpEndX, tmpEndY), getDXBrushForColour(System.Drawing.Color.Black, 12), fTotalWidth - fReduce);
                    }
                }

                _renderTarget.DrawLine(new SharpDX.Vector2(startX, startY), new SharpDX.Vector2(endX, endY), getDXBrushForColour(ni.Colour, 255), fStrokeWidth);

                if (ni.Setup)
                {
                    foreach (KeyValuePair<float, PointF> kvp in ni.ScaleCalibration)
                    {
                        PointF p = kvp.Value;
                        _renderTarget.FillEllipse(new Ellipse(new SharpDX.Vector2(x + (p.X * w), y + (p.Y * h)), 2f, 2f), getDXBrushForColour(System.Drawing.Color.Red, 255));
                    }
                }

                _renderTarget.PopAxisAlignedClip();
            }
            private double degToRad(float deg)
            {
                return (deg * Math.PI) / 180f;
            }
            private double radToDeg(float rad)
            {
                return rad * (180f / Math.PI);
            }
            private void getPerc(clsMeterItem mi, float value, out float percX, out float percY, out PointF min, out PointF max)
            {
                percX = 0;
                percY = 0;
                min = new PointF(0f, 0f);
                max = new PointF(0f, 0f);

                value = (float)Math.Round(value, 2); // NOTE: this will limit the finding of keys in the scale calibration below

                // adjust for >= 30mhz
                if (mi.ReadingSource == Reading.SIGNAL_STRENGTH || mi.ReadingSource == Reading.AVG_SIGNAL_STRENGTH)
                    value += MeterManager.dbmOffsetForAboveS9Frequency(_rx);

                // normalise to 100w
                else if (mi.NormaliseTo100W)
                {
                    //value *= MeterManager.normalisePower();
                    value *= (100 / mi.MaxPower);
                }

                clsMeterItem.clsPercCache pc = mi.GetPerc(value);
                if (pc != null)
                {                    
                    percX = pc._percX;
                    percY = pc._percY;
                    min = pc._min;
                    max = pc._max;
                    return;
                }

                if (mi.ScaleCalibration.Count > 0)
                {
                    // todo, only implemented for clockwise needles atm
                    PointF p_low = PointF.Empty;
                    PointF p_high = PointF.Empty;
                    float value_low = -200f;
                    float value_high = -200f;
                    bool bEqual = false;

                    if (mi.ScaleCalibration.ContainsKey(value))
                    {
                        p_low = mi.ScaleCalibration[value];
                        p_high = mi.ScaleCalibration[value];
                        value_low = value;
                        value_high = value;
                        bEqual = true;
                    }
                    else if (value < mi.ScaleCalibration.OrderBy(p => p.Key).First().Key) // below lowest
                    {
                        p_low = p_high = mi.ScaleCalibration.OrderBy(p => p.Key).First().Value;
                        value_low = value_high = mi.ScaleCalibration.OrderBy(p => p.Key).First().Key;
                        bEqual = true;
                    }
                    else if (value > mi.ScaleCalibration.OrderBy(p => p.Key).Last().Key) // above highest
                    {
                        p_low = p_high = mi.ScaleCalibration.OrderBy(p => p.Key).Last().Value;
                        value_low = value_high = mi.ScaleCalibration.OrderBy(p => p.Key).Last().Key;
                        bEqual = true;
                    }
                    else
                    {
                        //low side
                        foreach (KeyValuePair<float, PointF> kvp in mi.ScaleCalibration.OrderByDescending(p => p.Key))
                        {
                            if (kvp.Key <= value)
                            {
                                p_low = kvp.Value;
                                value_low = kvp.Key;
                                break;
                            }
                        }

                        // high
                        foreach (KeyValuePair<float, PointF> kvp in mi.ScaleCalibration.OrderBy(p => p.Key))
                        {
                            if (kvp.Key >= value)
                            {
                                p_high = kvp.Value;
                                value_high = kvp.Key;
                                break;
                            }
                        }
                    }
                    //get mins/maxs
                    float minX = (float)Math.Round(mi.ScaleCalibration.OrderBy(p => p.Value.X).First().Value.X, 3);
                    float maxX = (float)Math.Round(mi.ScaleCalibration.OrderByDescending(p => p.Value.X).First().Value.X, 3);

                    float minY = (float)Math.Round(mi.ScaleCalibration.OrderBy(p => p.Value.Y).First().Value.Y, 3);
                    float maxY = (float)Math.Round(mi.ScaleCalibration.OrderByDescending(p => p.Value.Y).First().Value.Y, 3);

                    float rangeX = maxX - minX;
                    float rangeY = maxY - minY;
                    percX = 0;
                    percY = 0;
                    if (bEqual)
                    {
                        if (rangeX > 0) percX = (p_low.X - minX) / rangeX;
                        if (rangeY > 0) percY = (p_low.Y - minY) / rangeY;
                    }
                    else
                    {
                        float value_range = value_high - value_low;
                        float ratio = (value - value_low) / value_range;
                        float newRangeX = p_high.X - p_low.X;
                        float newRangeY = p_high.Y - p_low.Y;

                        if (rangeX > 0) percX = ((p_low.X - minX) + (newRangeX * ratio)) / rangeX;
                        if (rangeY > 0) percY = ((p_low.Y - minY) + (newRangeY * ratio)) / rangeY;
                    }

                    percX = (float)Math.Round(percX, 3);
                    percY = (float)Math.Round(percY, 3);
                    min = new PointF(minX, minY);
                    max = new PointF(maxX, maxY);

                    pc = new clsMeterItem.clsPercCache() { _value = value,
                                                            _percX = percX,
                                                            _percY = percY,
                                                            _min = min,
                                                            _max = max };
                    mi.AddPerc(pc);
                }
            }            
            private SharpDX.Direct2D1.Bitmap bitmapFromSystemBitmap(RenderTarget rt, System.Drawing.Bitmap bitmap, string sId)
            {
                //[2.10.3.6]MW0LGE refactored to use Windows Imaging Component (WIC)
                try
                {
                    System.Drawing.Imaging.BitmapData bitmapData = null;

                    SharpDX.Direct2D1.Bitmap dxBitmap;
                    Size2 size = new Size2(bitmap.Width, bitmap.Height);
                    //int bpp = System.Drawing.Image.GetPixelFormatSize(bitmap.PixelFormat);
                    //int stride = (bitmap.Width * bpp + 7) / 8;

                    BitmapProperties bitmapProperties = new BitmapProperties(new SharpDX.Direct2D1.PixelFormat(Format.B8G8R8A8_UNorm, _ALPHA_MODE));

                    SharpDX.WIC.ImagingFactory factory = null;
                    SharpDX.WIC.BitmapDecoder decoder = null;
                    SharpDX.WIC.BitmapFrameDecode frame = null;
                    SharpDX.WIC.FormatConverter converter = null;

                    if (MeterManager.ContainsStreamData(sId))
                    {
                        MemoryStream tempStream = MeterManager.GetStreamData(sId);
                        tempStream.Position = 0;

                        try
                        {
                            factory = new SharpDX.WIC.ImagingFactory();
                            decoder = new SharpDX.WIC.BitmapDecoder(factory, tempStream, SharpDX.WIC.DecodeOptions.CacheOnDemand);
                            frame = decoder.GetFrame(0);
                            converter = new SharpDX.WIC.FormatConverter(factory);

                            converter.Initialize(frame, SharpDX.WIC.PixelFormat.Format32bppPRGBA);
                            dxBitmap = SharpDX.Direct2D1.Bitmap.FromWicBitmap(rt, converter);
                        }
                        finally
                        {
                            Utilities.Dispose(ref converter);
                            Utilities.Dispose(ref frame);
                            Utilities.Dispose(ref decoder);
                            Utilities.Dispose(ref factory);
                        }
                    }
                    else
                    {
                        bitmapData = bitmap.LockBits(
                            new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                            System.Drawing.Imaging.ImageLockMode.ReadOnly,
                            bitmap.PixelFormat);

                        MemoryStream ms = new MemoryStream();
                        bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                        ms.Position = 0;

                        try
                        {
                            factory = new SharpDX.WIC.ImagingFactory();
                            decoder = new SharpDX.WIC.BitmapDecoder(factory, ms, SharpDX.WIC.DecodeOptions.CacheOnDemand);
                            frame = decoder.GetFrame(0);
                            converter = new SharpDX.WIC.FormatConverter(factory);

                            converter.Initialize(frame, SharpDX.WIC.PixelFormat.Format32bppPRGBA);
                            dxBitmap = SharpDX.Direct2D1.Bitmap.FromWicBitmap(rt, converter);
                        }
                        finally
                        {
                            if (bitmapData != null) bitmap.UnlockBits(bitmapData);

                            Utilities.Dispose(ref converter);
                            Utilities.Dispose(ref frame);
                            Utilities.Dispose(ref decoder);
                            Utilities.Dispose(ref factory);
                        }

                        MeterManager.AddStreamData(sId, ms);
                    }

                    return dxBitmap;
                }
                catch (Exception ex)
                {
                    return null;
                }
            }            
        }        
        #endregion
    }
    public static class MultiMeterIO
    {
        private const int DELAY = 100; // msec
        [Serializable]
        public enum MMIODirection
        {
            IN = 0,
            OUT = 1,
            BOTH = 2
        }
        [Serializable]
        public enum MMIOFormat
        {
            JSON = 0,
            XML = 1,
            RAW = 2,
            LAST = 3
        }
        [Serializable]
        public enum MMIOType
        {
            UDP_LISTENER = 0,
            TCPIP_LISTENER = 1,
            SERIAL = 2,
            TCPIP_CLIENT = 3,
            REQUESTER = 4
        }
        [Serializable]
        public enum MMIOTerminator
        {
            NONE = 0,
            CR = 1,
            LF = 2,
            CRLF = 3,
            CUSTOM = 4,
            LAST = 5
        }
        [Serializable]
        public class clsMMIO
        {
            private Guid _guid;
            private MMIODirection _direction;
            private string _ip;
            private int _port;
            private string _udp_endpoint_ip;
            private int _udp_endpoint_port;
            private MMIOFormat _format_in;
            private MMIOFormat _format_out;
            private MMIOType _type;
            private bool _listener_active;
            private string _four_char;
            private bool _enabled;
            private bool _connector_running;
            private MMIOTerminator _terminator_in;
            private MMIOTerminator _terminator_out;
            private string _custom_terminator_in;
            private string _custom_terminator_out;
            private string _custom_terminator_parsed_in;
            private string _custom_terminator_parsed_out;
            private IPEndPoint _udp_endpoint;

            //
            private string _com_port;
            private int _baud_rate;
            private int _data_bits;
            private StopBits _stop_bits;
            private Parity _parity;
            //

            private ConcurrentDictionary<string, object> _io_variables;
            private ConcurrentQueue<string> _outbound_queue;
            public clsMMIO()
            {
                init();

                _four_char = FourChar(_ip, _port, _guid);
            }
            private void init()
            {
                _enabled = true;
                _guid = Guid.NewGuid();
                _type = MMIOType.UDP_LISTENER;
                _ip = "127.0.0.1";
                _port = 9000;
                _udp_endpoint_ip = "127.0.0.1";
                _udp_endpoint_port = 10000;
                _listener_active = false;
                _format_in = MMIOFormat.JSON;
                _format_out = MMIOFormat.JSON;
                _direction = MMIODirection.IN;
                _terminator_in = MMIOTerminator.NONE;
                _terminator_out = MMIOTerminator.NONE;
                _custom_terminator_in = "";
                _custom_terminator_out = "";
                _custom_terminator_parsed_in = "";
                _custom_terminator_parsed_out = "";
                _udp_endpoint = null;

                _com_port = "";
                _baud_rate = 9600;
                _data_bits = 8;
                _stop_bits = StopBits.One;
                _parity = Parity.None;

                _io_variables = new ConcurrentDictionary<string, object>();
                _outbound_queue = new ConcurrentQueue<string>();                
            }
            public clsMMIO(MMIOType type, string ip, int port, bool enabled)
            {
                init();

                _enabled = enabled;
                _type = type;
                _ip = ip;
                _port = port;

                _four_char = FourChar(_ip, _port, _guid);
            }
            public clsMMIO(MMIOType type, string com_port, int baud_rate, int data_bits, StopBits stop_bits, Parity parity, bool enabled)
            {
                init();

                _enabled = enabled;
                _type = type;
                _ip = "";
                _port = 0;

                _com_port = com_port;
                _baud_rate = baud_rate;
                _data_bits = data_bits;
                _stop_bits = stop_bits;
                _parity = parity;

                _four_char = FourChar(com_port, baud_rate + data_bits, _guid);
            }
            public Guid Guid
            {
                get { return _guid; }
                set { 
                    _guid = value;
                    _four_char = FourChar(_ip, _port, _guid); // update the four char if guid is assigned, unlike ip and port
                }
            }
            public MMIODirection Direction
            {
                get { return _direction; }
                set { 
                    _direction = value;
                    refreshUdpEndpoint();
                }
            }
            public string IP
            {
                get { return _ip; }
                set { 
                    _ip = value;
                    if (string.IsNullOrEmpty(_four_char)) _four_char = FourChar(_ip, _port, _guid);
                }
            }
            public int Port
            {
                get { return _port; }
                set { 
                    _port = value;
                    if(string.IsNullOrEmpty(_four_char)) _four_char = FourChar(_ip, _port, _guid);
                }
            }
            public string UdpEndpointIP
            {
                get { return _udp_endpoint_ip; }
                set
                {
                    _udp_endpoint_ip = value;
                    refreshUdpEndpoint();
                }
            }
            public int UdpEndpointPort
            {
                get { return _udp_endpoint_port; }
                set
                {
                    _udp_endpoint_port = value;
                    refreshUdpEndpoint();
                }
            }
            public string ComPort
            {
                get { return _com_port; }
                set { _com_port = value; }
            }
            public int BaudRate
            {
                get { return _baud_rate; }
                set { _baud_rate = value; }
            }
            public int DataBits
            {
                get { return _data_bits; }
                set { _data_bits = value; }
            }
            public StopBits StopBits
            {
                get { return _stop_bits; }
                set { _stop_bits = value; }
            }
            public Parity Parity
            {
                get { return _parity; }
                set { _parity = value; }
            }
            private void refreshUdpEndpoint()
            {
                if (_direction == MMIODirection.OUT || _direction == MMIODirection.BOTH)
                    _udp_endpoint = new IPEndPoint(IPAddress.Parse(_udp_endpoint_ip), _udp_endpoint_port);
                else
                {
                    _udp_endpoint = null;
                }
            }
            public IPEndPoint UDPEndPoint
            {
                get { return _udp_endpoint; }
            }
            public MMIOFormat FormatIn
            {
                get { return _format_in; }
                set { _format_in = value; }
            }
            public MMIOFormat FormatOut
            {
                get { return _format_out; }
                set { _format_out = value; }
            }
            public MMIOTerminator TerminatorIn
            {
                get { return _terminator_in; }
                set { _terminator_in = value; }
            }
            public MMIOTerminator TerminatorOut
            {
                get { return _terminator_out; }
                set { _terminator_out = value; }
            }
            public MMIOType Type
            {
                get { return _type; }
                set { _type = value; }
            }
            public bool Active
            {
                get { return _listener_active; }
                set { _listener_active = value; }
            }
            public string FourChar
            {
                get { return _four_char; }
            }
            public bool Enabled
            {
                get { return _enabled; }
                set { _enabled = value; }
            }
            public string CustomTerminatorParsedIn
            {
                get { return string.IsNullOrEmpty(_custom_terminator_parsed_in) ? "\0" : _custom_terminator_parsed_in; }
            }
            public string CustomTerminatorIn
            {
                get { return _custom_terminator_in; }
                set
                {
                    _custom_terminator_in = value;

                    _custom_terminator_parsed_in = _custom_terminator_in.Replace(@"\n", "\n"); // use @ so that it is a literal verbatim string
                    _custom_terminator_parsed_in = _custom_terminator_parsed_in.Replace(@"\r", "\r");
                    _custom_terminator_parsed_in = _custom_terminator_parsed_in.Replace(@"\0", "\0");
                }
            }
            public void EnqueueOutbound(string data)
            {
                if (_direction == MMIODirection.IN) return;
                _outbound_queue.Enqueue(data);
            }
            public string DequeueOutbound()
            {
                if (_direction == MMIODirection.IN) return "";

                bool ok = _outbound_queue.TryDequeue(out string data);
                if (ok) return data;
                return "";
            }
            public bool OutboundQueueEmpty
            {
                get { return _outbound_queue.Count == 0; }
            }
            public string CustomTerminatorParsedOut
            {
                get { return string.IsNullOrEmpty(_custom_terminator_parsed_out) ? "\0" : _custom_terminator_parsed_out; }
            }
            public string CustomTerminatorOut
            {
                get { return _custom_terminator_out; }
                set
                {
                    _custom_terminator_out = value;

                    _custom_terminator_parsed_out = _custom_terminator_out.Replace(@"\n", "\n"); // use @ so that it is a literal verbatim string
                    _custom_terminator_parsed_out = _custom_terminator_parsed_out.Replace(@"\r", "\r");
                    _custom_terminator_parsed_out = _custom_terminator_parsed_out.Replace(@"\0", "\0");
                }
            }
            public bool StartConnection()
            {
                bool ok = false;
                switch(_type)
                {
                    case MMIOType.UDP_LISTENER:
                        refreshUdpEndpoint();
                        ok = MultiMeterIO.StartListeningUDP(this);
                        break;
                    case MMIOType.TCPIP_LISTENER:
                        ok = MultiMeterIO.StartListeningTCPIP(this);
                        break;
                    case MMIOType.TCPIP_CLIENT:
                        ok = MultiMeterIO.StartTcpClient(this);
                        break;
                    case MMIOType.SERIAL:
                        ok = MultiMeterIO.StartSerialPort(this);
                        break;
                }
                return ok;
            }
            public void StopConnection()
            {
                MultiMeterIO.StopConnection(_guid);
            }
            public bool SetVariable(string key, object value)
            {
                bool ok;
                if (_io_variables.ContainsKey(key))
                {
                    _io_variables[key] = value;
                    ok = true;
                }
                else
                {
                    ok = _io_variables.TryAdd(key, value);
                }
                return ok;
            }
            public object GetVariable(string key, string precision_format = "")
            {
                if (_io_variables.ContainsKey(key))
                {
                    if (precision_format == "") precision_format = "0.0#####";
                    object val = _io_variables[key];
                    string sTmp;
                    if (val is int)
                        sTmp = val.ToString();
                    else if (val is float)
                        sTmp = ((float)val).ToString(precision_format);
                    else if (val is double)
                        sTmp = ((double)val).ToString(precision_format);
                    else if (val is bool)
                        sTmp = ((bool)val).ToString().ToLower();
                    else
                        sTmp = val.ToString();

                    Type valueType = DetermineType(sTmp);
                    object covertedVal = ConvertToType(sTmp, valueType);
                    return covertedVal;
                }
                return false;
            }
            public Type DetermineType(string value)
            {
                // Create culture info for European style numbers
                CultureInfo europeanCulture = new CultureInfo("fr-FR");
                NumberStyles numberStyle = NumberStyles.Float | NumberStyles.AllowThousands;

                // Try parsing as an integer
                if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out _) ||
                    int.TryParse(value, NumberStyles.Integer, europeanCulture, out _))
                {
                    return typeof(int);
                }
                // Try parsing as a float
                else if (float.TryParse(value, numberStyle, CultureInfo.InvariantCulture, out _) ||
                         float.TryParse(value, numberStyle, europeanCulture, out _))
                {
                    // Check for number of decimal places
                    if (value.Contains(".") || value.Contains(","))
                    {
                        char decimalSeparator = value.Contains(".") ? '.' : ',';
                        string[] parts = value.Split(decimalSeparator);

                        if (parts.Length == 2 && parts[1].Length > 7)
                        {
                            // Return double if more than 7 decimal places
                            return typeof(double);
                        }
                    }

                    // Return float if 7 or fewer decimal places
                    return typeof(float);
                }
                // Try parsing as a double
                else if (double.TryParse(value, numberStyle, CultureInfo.InvariantCulture, out _) ||
                         double.TryParse(value, numberStyle, europeanCulture, out _))
                {
                    return typeof(double);
                }
                // Try parsing as a boolean
                else if (bool.TryParse(value, out _))
                {
                    return typeof(bool);
                }
                // Default to string
                else
                {
                    return typeof(string);
                }
            }
            //[DebuggerHidden]
            public object ConvertToType(string value, Type type)
            {
                CultureInfo europeanCulture = new CultureInfo("fr-FR");
                CultureInfo invariantCulture = CultureInfo.InvariantCulture;

                try
                {
                    TypeConverter converter = TypeDescriptor.GetConverter(type);
                    try
                    {
                        return converter.ConvertFromString(null, invariantCulture, value);
                    }
                    catch
                    {
                        try
                        {
                            return converter.ConvertFromString(null, CultureInfo.CurrentCulture, value);
                        }
                        catch
                        {
                            return converter.ConvertFromString(null, europeanCulture, value);
                        }
                    }
                }
                catch (Exception)
                {
                    if (type.IsEnum)
                    {
                        return Enum.Parse(type, value);
                    }

                    throw new ArgumentException("Cannot convert the string to the specified type.", nameof(value));
                }
            }
            public ConcurrentDictionary<string, object> Variables()
            {
                return _io_variables;
            }
            public string VariableValueType(object obj, string float_precision = "")
            {
                if (float_precision == "") float_precision = "0.0#####";
                string tmp;
                if (obj is int)
                    tmp = ((int)obj).ToString();
                else if (obj is float)
                    tmp = ((float)obj).ToString(float_precision);
                else if (obj is double)
                    tmp = ((double)obj).ToString(float_precision);
                else if (obj is bool)
                    tmp = ((bool)obj).ToString().ToLower();
                else
                    tmp = obj.ToString();
                return tmp;
            }
            //private static T deepCopy<T>(T obj)
            //{
            //    if (ReferenceEquals(obj, null) || !typeof(T).IsSerializable)
            //    {
            //        return default(T);
            //    }

            //    IFormatter formatter = new BinaryFormatter();
            //    using (Stream stream = new MemoryStream())
            //    {
            //        formatter.Serialize(stream, obj);
            //        stream.Seek(0, SeekOrigin.Begin);
            //        return (T)formatter.Deserialize(stream);
            //    }
            //}
            public void RemoveVariable(string key)
            {
                _io_variables.TryRemove(key, out _);
            }
        }
        private class TcpListener
        {
            private Guid _guid;
            private MMIOType _type;
            private string _ip;
            private int _port;
            private System.Net.Sockets.TcpListener _tcpListener;
            private TcpClient _tcpClient;
            private Thread _listenerThread;
            private volatile bool _isRunning;

            public event Action<Guid, string> ReceivedDataString;
            public event Action<Guid> TransmittedData;
            public event Action<Guid, MMIOType, bool> ConnectorRunning;

            public TcpListener(Guid guid, MMIOType type, string ip, int port)
            {
                _guid = guid;
                _type = type;
                _ip = ip;
                _port = port;
                _tcpListener = new System.Net.Sockets.TcpListener(IPAddress.Parse(ip), port);
            }

            public void Start()
            {
                _isRunning = true;
                _listenerThread = new Thread(new ThreadStart(listen));
                _listenerThread.IsBackground = true;
                _listenerThread.Start();                
                ConnectorRunning?.Invoke(_guid, _type, true);
            }

            public void Stop()
            {
                _isRunning = false;
                if (_tcpClient != null)
                {
                    _tcpClient.Close();
                }
                _tcpListener.Stop();
                _listenerThread.Join();
                ConnectorRunning?.Invoke(_guid, _type, false);
            }
            private bool clientConnected
            {
                get
                {
                    try
                    {
                        return _tcpClient.Connected;
                    }
                    catch
                    {
                        return false;
                    }
                }
            }
            private void listen()
            {                
                try
                {
                    bool started = true;
                    DateTime lastTimeActive = DateTime.Now;
                    _tcpListener.Start();
                    while (_isRunning)
                    {
                        if (!_tcpListener.Pending())
                        {
                            Thread.Sleep(50);
                            continue;
                        }

                        try
                        {
                            _tcpClient = _tcpListener.AcceptTcpClient();
                            _tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
                            // stop listening for new
                            if (started)
                            {
                                _tcpListener.Stop();
                                started = false;
                            }

                            NetworkStream stream = _tcpClient.GetStream();
                            string bufferConcat = "";
                            while (clientConnected && _isRunning)
                            {
                                bool sleep = true;
                                bool inbound = _mmio_data[_guid].Direction == MMIODirection.IN || _mmio_data[_guid].Direction == MMIODirection.BOTH;
                                bool outbound = _mmio_data[_guid].Direction == MMIODirection.OUT || _mmio_data[_guid].Direction == MMIODirection.BOTH;

                                if (inbound)
                                {
                                    try
                                    {
                                        if (stream.DataAvailable)
                                        {
                                            byte[] buffer = new byte[1024];
                                            int bytesRead = stream.Read(buffer, 0, buffer.Length);
                                            if (bytesRead > 0)
                                            {
                                                lastTimeActive = DateTime.Now;
                                                string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                                                sleep = false;
                                                string term = "";
                                                switch (_mmio_data[_guid].TerminatorIn)
                                                {
                                                    case MMIOTerminator.NONE:
                                                        term = "";
                                                        break;
                                                    case MMIOTerminator.CR:
                                                        term = "\r";
                                                        break;
                                                    case MMIOTerminator.LF:
                                                        term = "\n";
                                                        break;
                                                    case MMIOTerminator.CRLF:
                                                        term = "\r\n";
                                                        break;
                                                    case MMIOTerminator.CUSTOM:
                                                        term = _mmio_data[_guid].CustomTerminatorParsedIn;
                                                        break;
                                                }
                                                bufferConcat += receivedData;                                                
                                                if (string.IsNullOrEmpty(term))
                                                {
                                                    ReceivedDataString?.Invoke(_guid, bufferConcat);
                                                    bufferConcat = "";
                                                }
                                                else
                                                {
                                                    //int pos = receivedData.IndexOf(term);
                                                    int pos = bufferConcat.IndexOf(term);
                                                    while (pos > -1)
                                                    {
                                                        ReceivedDataString?.Invoke(_guid, bufferConcat.Substring(0, pos));
                                                        bufferConcat = bufferConcat.Substring(pos + term.Length);
                                                        pos = bufferConcat.IndexOf(term);
                                                    }
                                                }

                                                if (bufferConcat.Length >= 4096 * 8) bufferConcat = bufferConcat.Substring(4096 * 4); // some limiter incase it is not being processed
                                            }
                                        }
                                    }
                                    catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                    {
                                        // Handle disconnection or stream issues
                                        break;
                                    }
                                }
                                if (outbound)
                                {
                                    if (!_mmio_data[_guid].OutboundQueueEmpty)
                                    {
                                        string outData = "";
                                        int n = 0;
                                        string termSend = "";
                                        switch (_mmio_data[_guid].TerminatorOut)
                                        {
                                            case MMIOTerminator.NONE:
                                                termSend = "";
                                                break;
                                            case MMIOTerminator.CR:
                                                termSend = "\r";
                                                break;
                                            case MMIOTerminator.LF:
                                                termSend = "\n";
                                                break;
                                            case MMIOTerminator.CRLF:
                                                termSend = "\r\n";
                                                break;
                                            case MMIOTerminator.CUSTOM:
                                                termSend = _mmio_data[_guid].CustomTerminatorParsedOut;
                                                break;
                                        }
                                        while (!_mmio_data[_guid].OutboundQueueEmpty)
                                        {
                                            outData += _mmio_data[_guid].DequeueOutbound() + termSend;
                                            n++;
                                            if (n == 20) break; // some limit
                                        }
                                        if (outData.Length > 0)
                                        {
                                            lastTimeActive = DateTime.Now;
                                            sleep = false;

                                            Byte[] sendBytes = Encoding.ASCII.GetBytes(outData);
                                            try
                                            {
                                                stream.Write(sendBytes, 0, sendBytes.Length);
                                                TransmittedData?.Invoke(_guid);
                                            }
                                            catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                            {
                                                // Handle disconnection or stream issues
                                                break;
                                            }
                                        }
                                    }
                                }

                                // heatbeat connection connected checker
                                if ((DateTime.Now - lastTimeActive).TotalMilliseconds > 5000 && clientConnected)
                                {
                                    // at least 5 seconds since an rx or a tx, we should tx a byte, just to check connection state
                                    Byte[] sendBytes = Encoding.ASCII.GetBytes("\0");
                                    try
                                    {
                                        stream.Write(sendBytes, 0, sendBytes.Length);
                                        lastTimeActive = DateTime.Now;
                                    }
                                    catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                    {
                                        // Handle disconnection or stream issues
                                        break;
                                    }
                                }

                                if (sleep)
                                    Thread.Sleep(50);
                                else
                                    Thread.Sleep(1);

                            }
                            if (_isRunning && !started)
                            {
                                _tcpListener.Start();
                            }
                        }
                        catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                        {
                            // Handle client connection acceptance issues
                            if (_tcpClient != null)
                            {
                                _tcpClient.Close();
                            }
                        }
                    }
                }
                catch (Exception ex) when (ex is SocketException || ex is ObjectDisposedException)
                {
                    // Handle listener stopping issues
                }
                catch (Exception ex)
                {
                    Debug.Print($">>>>>>   Exception in listener {_guid}: {ex.Message}");
                }
                finally
                {
                    if (_tcpClient != null)
                    {
                        _tcpClient.Close();
                    }
                    _tcpListener.Stop();
                }
            }
        }
        public class TcpClientHandler
        {
            private Guid _guid;
            private MMIOType _type;
            private string _ip;
            private int _port;
            private TcpClient _tcpClient;
            private Thread _clientThread;
            private volatile bool _isRunning;
            private NetworkStream _networkStream;

            public event Action<Guid, string> ReceivedDataString;
            public event Action<Guid> TransmittedData;
            public event Action<Guid, MMIOType, bool> ConnectorRunning;

            public TcpClientHandler(Guid guid, MMIOType type, string ip, int port)
            {
                _guid = guid;
                _type = type;
                _ip = ip;
                _port = port;
                //_tcpClient = new TcpClient();
            }

            public void Start()
            {
                _isRunning = true;
                _clientThread = new Thread(new ThreadStart(Connect));
                _clientThread.IsBackground = true;
                _clientThread.Start();
                ConnectorRunning?.Invoke(_guid, _type, true);
            }

            public void Stop()
            {
                _isRunning = false;
                if (_tcpClient != null)
                {
                    _tcpClient.Close();
                }
                _clientThread.Join();
                ConnectorRunning?.Invoke(_guid, _type, false);
            }
            private bool clientConnected
            {
                get
                {
                    try
                    {
                        return _tcpClient.Connected;
                    }
                    catch
                    {
                        return false;
                    }
                }
            }
            private void Connect()
            {
                bool reconnect = true;
                while (_isRunning && reconnect)
                {
                    try
                    {
                        reconnect = false;
                        _tcpClient = new TcpClient();
                        _tcpClient.Connect(_ip, _port);
                        _networkStream = _tcpClient.GetStream();

                        DateTime lastTimeActive = DateTime.Now;
                        string bufferConcat = "";

                        while (_isRunning)
                        {
                            if (!clientConnected)
                            {
                                break;
                            }

                            bool sleep = true;
                            bool inbound = _mmio_data[_guid].Direction == MMIODirection.IN || _mmio_data[_guid].Direction == MMIODirection.BOTH;
                            bool outbound = _mmio_data[_guid].Direction == MMIODirection.OUT || _mmio_data[_guid].Direction == MMIODirection.BOTH;

                            if (inbound)
                            {
                                try
                                {
                                    if (_networkStream.DataAvailable)
                                    {
                                        byte[] buffer = new byte[1024];
                                        int bytesRead = _networkStream.Read(buffer, 0, buffer.Length);
                                        if (bytesRead > 0)
                                        {
                                            lastTimeActive = DateTime.Now;
                                            string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                                            sleep = false;
                                            string term = "";
                                            switch (_mmio_data[_guid].TerminatorIn)
                                            {
                                                case MMIOTerminator.NONE:
                                                    term = "";
                                                    break;
                                                case MMIOTerminator.CR:
                                                    term = "\r";
                                                    break;
                                                case MMIOTerminator.LF:
                                                    term = "\n";
                                                    break;
                                                case MMIOTerminator.CRLF:
                                                    term = "\r\n";
                                                    break;
                                                case MMIOTerminator.CUSTOM:
                                                    term = _mmio_data[_guid].CustomTerminatorParsedIn;
                                                    break;
                                            }
                                            bufferConcat += receivedData;
                                            if (string.IsNullOrEmpty(term))
                                            {
                                                ReceivedDataString?.Invoke(_guid, bufferConcat);
                                                bufferConcat = "";
                                            }
                                            else
                                            {
                                                //int pos = receivedData.IndexOf(term);
                                                int pos = bufferConcat.IndexOf(term);
                                                while (pos > -1)
                                                {
                                                    ReceivedDataString?.Invoke(_guid, bufferConcat.Substring(0, pos));
                                                    bufferConcat = bufferConcat.Substring(pos + term.Length);
                                                    pos = bufferConcat.IndexOf(term);
                                                }
                                            }

                                            if (bufferConcat.Length >= 4096 * 8) bufferConcat = bufferConcat.Substring(4096 * 4);
                                        }
                                    }
                                }
                                catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                {
                                    if (_tcpClient != null && !clientConnected)
                                        reconnect = true;

                                    break;
                                }
                            }
                            if (outbound)
                            {
                                if (!_mmio_data[_guid].OutboundQueueEmpty)
                                {
                                    string outData = "";
                                    int n = 0;
                                    string termSend = "";
                                    switch (_mmio_data[_guid].TerminatorOut)
                                    {
                                        case MMIOTerminator.NONE:
                                            termSend = "";
                                            break;
                                        case MMIOTerminator.CR:
                                            termSend = "\r";
                                            break;
                                        case MMIOTerminator.LF:
                                            termSend = "\n";
                                            break;
                                        case MMIOTerminator.CRLF:
                                            termSend = "\r\n";
                                            break;
                                        case MMIOTerminator.CUSTOM:
                                            termSend = _mmio_data[_guid].CustomTerminatorParsedOut;
                                            break;
                                    }
                                    while (!_mmio_data[_guid].OutboundQueueEmpty)
                                    {
                                        outData += _mmio_data[_guid].DequeueOutbound() + termSend;
                                        n++;
                                        if (n == 20) break;
                                    }
                                    if (outData.Length > 0)
                                    {
                                        lastTimeActive = DateTime.Now;
                                        sleep = false;

                                        Byte[] sendBytes = Encoding.ASCII.GetBytes(outData);
                                        try
                                        {
                                            _networkStream.Write(sendBytes, 0, sendBytes.Length);
                                            TransmittedData?.Invoke(_guid);
                                        }
                                        catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                        {
                                            if (_tcpClient != null && !clientConnected)
                                                reconnect = true;

                                            break;
                                        }
                                    }
                                }
                            }

                            if ((DateTime.Now - lastTimeActive).TotalMilliseconds > 5000 && clientConnected)
                            {
                                Byte[] sendBytes = Encoding.ASCII.GetBytes("\0");
                                try
                                {
                                    _networkStream.Write(sendBytes, 0, sendBytes.Length);
                                    lastTimeActive = DateTime.Now;
                                }
                                catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                {
                                    if (_tcpClient != null && !clientConnected)
                                        reconnect = true;

                                    break;
                                }
                            }

                            if (sleep)
                                Thread.Sleep(50);
                            else
                                Thread.Sleep(1);
                        }
                    }
                    catch (Exception ex) when (ex is SocketException || ex is ObjectDisposedException)
                    {
                        if (_tcpClient != null && !clientConnected)
                            reconnect = true;
                    }
                    catch (Exception ex)
                    {
                        if (_tcpClient != null && !clientConnected)
                            reconnect = true;
                    }
                    finally
                    {
                        if (_tcpClient != null)
                        {
                            _tcpClient.Close();
                        }
                    }

                    if (reconnect)
                        Thread.Sleep(100);
                }
            }
        }
        private class UdpListener
        {
            private Guid _guid;
            private string _ip;
            private int _port;
            private MMIOType _type;
            private UdpClient _udpClient;
            private Thread _listenerThread;
            private volatile bool _isRunning;

            public event Action<Guid, string> ReceivedDataString;
            public event Action<Guid> TransmittedData;
            public event Action<Guid, MMIOType, bool> ConnectorRunning;

            public UdpListener(Guid guid, MMIOType type, string ip, int port)
            {
                _guid = guid;
                _type = type;
                _ip = ip;
                _port = port;
                _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(ip), port));
            }

            public void Start()
            {
                _isRunning = true;
                _listenerThread = new Thread(new ThreadStart(listen));
                _listenerThread.IsBackground = true;
                _listenerThread.Start();
                ConnectorRunning?.Invoke(_guid, _type, true);
            }

            public void Stop()
            {
                _isRunning = false;
                _udpClient.Close();
                _listenerThread.Join();
                ConnectorRunning?.Invoke(_guid, _type, false);
            }

            private void listen()
            {
                string bufferConcat = "";
                UdpClient udpSenderClient = null;

                try
                {
                    while (_isRunning)
                    {
                        bool inbound = _mmio_data[_guid].Direction == MMIODirection.IN || _mmio_data[_guid].Direction == MMIODirection.BOTH;
                        bool outbound = _mmio_data[_guid].Direction == MMIODirection.OUT || _mmio_data[_guid].Direction == MMIODirection.BOTH;
                        bool sleep = true;

                        if (inbound)
                        {
                            try
                            {
                                if (_udpClient.Available > 0)
                                {
                                    IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                                    byte[] data = _udpClient.Receive(ref remoteEndPoint);
                                    string receivedData = Encoding.UTF8.GetString(data);                                    
                                    sleep = false;
                                    string term = "";
                                    switch (_mmio_data[_guid].TerminatorIn)
                                    {
                                        case MMIOTerminator.NONE:
                                            term = "";
                                            break;
                                        case MMIOTerminator.CR:
                                            term = "\r";
                                            break;
                                        case MMIOTerminator.LF:
                                            term = "\n";
                                            break;
                                        case MMIOTerminator.CRLF:
                                            term = "\r\n";
                                            break;
                                        case MMIOTerminator.CUSTOM:
                                            term = _mmio_data[_guid].CustomTerminatorParsedIn;
                                            break;
                                    }
                                    bufferConcat += receivedData;                                    
                                    if (string.IsNullOrEmpty(term))
                                    {
                                        ReceivedDataString?.Invoke(_guid, bufferConcat);
                                        bufferConcat = "";
                                    }
                                    else
                                    {
                                        //int pos = receivedData.IndexOf(term);
                                        int pos = bufferConcat.IndexOf(term);
                                        while (pos > -1)
                                        {
                                            ReceivedDataString?.Invoke(_guid, bufferConcat.Substring(0, pos));
                                            bufferConcat = bufferConcat.Substring(pos + term.Length);
                                            pos = bufferConcat.IndexOf(term);
                                        }
                                    }

                                    if (bufferConcat.Length >= 4096 * 8) bufferConcat = bufferConcat.Substring(4096 * 4); // some limiter incase it is not being processed
                                }
                            }
                            catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                            {
                                if (!_isRunning) break;
                                //Debug.Print($">>>>>>   Socket Exception in listener {_guid}: {ex.Message}");
                            }
                        }
                        if (outbound && _mmio_data[_guid].UDPEndPoint != null)
                        {
                            if (udpSenderClient == null)
                            {
                                udpSenderClient = new UdpClient();
                                udpSenderClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true);
                            }
                            if (!_mmio_data[_guid].OutboundQueueEmpty)
                            {
                                string outData = "";
                                int n = 0;
                                string termSend = "";
                                switch (_mmio_data[_guid].TerminatorOut)
                                {
                                    case MMIOTerminator.NONE:
                                        termSend = "";
                                        break;
                                    case MMIOTerminator.CR:
                                        termSend = "\r";
                                        break;
                                    case MMIOTerminator.LF:
                                        termSend = "\n";
                                        break;
                                    case MMIOTerminator.CRLF:
                                        termSend = "\r\n";
                                        break;
                                    case MMIOTerminator.CUSTOM:
                                        termSend = _mmio_data[_guid].CustomTerminatorParsedOut;
                                        break;
                                }
                                while (!_mmio_data[_guid].OutboundQueueEmpty)
                                {
                                    outData += _mmio_data[_guid].DequeueOutbound() + termSend;
                                    n++;
                                    if (n == 20) break; // some limit
                                }
                                if (outData.Length > 0)
                                {
                                    sleep = false;

                                    Byte[] sendBytes = Encoding.ASCII.GetBytes(outData);
                                    try
                                    {
                                        udpSenderClient.Send(sendBytes, sendBytes.Length, _mmio_data[_guid].UDPEndPoint);
                                        TransmittedData?.Invoke(_guid);
                                    }
                                    catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                                    {
                                        if (!_isRunning) break;
                                    }
                                }
                            }
                        }
                        else if (udpSenderClient != null)
                        {
                            try
                            {
                                udpSenderClient.Close();
                            }
                            catch (Exception ex) when (ex is SocketException || ex is IOException || ex is ObjectDisposedException)
                            {
                            }
                            udpSenderClient = null;
                        }

                        if (sleep)
                            Thread.Sleep(50);
                        else
                            Thread.Sleep(1);
                    }
                }
                catch (Exception ex)
                {
                    Debug.Print($">>>>>>   Exception in listener {_guid}: {ex.Message}");
                }
            }
        }
        public class SerialPortHandler
        {
            private Guid _guid;
            private MMIOType _type;
            private string _comPort;
            private int _baudRate;
            private int _dataBits;
            private StopBits _stopBits;
            private Parity _parity;
            private SerialPort _serialPort;
            private Thread _serialThread;
            private volatile bool _isRunning;

            public event Action<Guid, string> ReceivedDataString;
            public event Action<Guid> TransmittedData;
            public event Action<Guid, MMIOType, bool> ConnectorRunning;

            public SerialPortHandler(Guid guid, MMIOType type, string comPort, int baudRate, int dataBits, StopBits stopBits, Parity parity)
            {
                _guid = guid;
                _type = type;
                _comPort = comPort;
                _baudRate = baudRate;
                _dataBits = dataBits;
                _stopBits = stopBits;
                _parity = parity;
                _serialPort = new SerialPort(comPort, baudRate, parity, dataBits, stopBits);
                _serialPort.ReadTimeout = 1000;
                _serialPort.WriteTimeout = 1000;
            }

            public void Start()
            {
                _isRunning = true;
                _serialThread = new Thread(new ThreadStart(Connect));
                _serialThread.IsBackground = true;
                _serialThread.Start();
                ConnectorRunning?.Invoke(_guid, _type, true);
            }

            public void Stop()
            {
                _isRunning = false;
                if (_serialPort != null && _serialPort.IsOpen)
                {
                    _serialPort.Close();
                }
                _serialThread.Join();
                ConnectorRunning?.Invoke(_guid, _type, false);
            }

            private void Connect()
            {
                try
                {
                    _serialPort.Open();

                    DateTime lastTimeActive = DateTime.Now;
                    string bufferConcat = "";

                    while (_isRunning)
                    {
                        if (!_serialPort.IsOpen)
                        {
                            break;
                        }

                        bool sleep = true;
                        bool inbound = _mmio_data[_guid].Direction == MMIODirection.IN || _mmio_data[_guid].Direction == MMIODirection.BOTH;
                        bool outbound = _mmio_data[_guid].Direction == MMIODirection.OUT || _mmio_data[_guid].Direction == MMIODirection.BOTH;

                        if (inbound)
                        {
                            try
                            {
                                if (_serialPort.BytesToRead > 0)
                                {
                                    byte[] buffer = new byte[1024];
                                    int bytesRead = _serialPort.Read(buffer, 0, buffer.Length);
                                    if (bytesRead > 0)
                                    {
                                        lastTimeActive = DateTime.Now;
                                        string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                                        sleep = false;
                                        string term = "";
                                        switch (_mmio_data[_guid].TerminatorIn)
                                        {
                                            case MMIOTerminator.NONE:
                                                term = "";
                                                break;
                                            case MMIOTerminator.CR:
                                                term = "\r";
                                                break;
                                            case MMIOTerminator.LF:
                                                term = "\n";
                                                break;
                                            case MMIOTerminator.CRLF:
                                                term = "\r\n";
                                                break;
                                            case MMIOTerminator.CUSTOM:
                                                term = _mmio_data[_guid].CustomTerminatorParsedIn;
                                                break;
                                        }
                                        bufferConcat += receivedData;
                                        if (string.IsNullOrEmpty(term))
                                        {
                                            ReceivedDataString?.Invoke(_guid, bufferConcat);
                                            bufferConcat = "";
                                        }
                                        else
                                        {
                                            //int pos = receivedData.IndexOf(term);
                                            int pos = bufferConcat.IndexOf(term);
                                            while (pos > -1)
                                            {
                                                ReceivedDataString?.Invoke(_guid, bufferConcat.Substring(0, pos));
                                                bufferConcat = bufferConcat.Substring(pos + term.Length);
                                                pos = bufferConcat.IndexOf(term);
                                            }
                                        }

                                        if (bufferConcat.Length >= 4096 * 8) bufferConcat = bufferConcat.Substring(4096 * 4);
                                    }
                                }
                            }
                            catch (Exception ex) when (ex is IOException || ex is InvalidOperationException)
                            {
                                break;
                            }
                        }
                        if (outbound)
                        {
                            if (!_mmio_data[_guid].OutboundQueueEmpty)
                            {
                                string outData = "";
                                int n = 0;
                                string termSend = "";
                                switch (_mmio_data[_guid].TerminatorOut)
                                {
                                    case MMIOTerminator.NONE:
                                        termSend = "";
                                        break;
                                    case MMIOTerminator.CR:
                                        termSend = "\r";
                                        break;
                                    case MMIOTerminator.LF:
                                        termSend = "\n";
                                        break;
                                    case MMIOTerminator.CRLF:
                                        termSend = "\r\n";
                                        break;
                                    case MMIOTerminator.CUSTOM:
                                        termSend = _mmio_data[_guid].CustomTerminatorParsedOut;
                                        break;
                                }
                                while (!_mmio_data[_guid].OutboundQueueEmpty)
                                {
                                    outData += _mmio_data[_guid].DequeueOutbound() + termSend;
                                    n++;
                                    if (n == 20) break;
                                }
                                if (outData.Length > 0)
                                {
                                    lastTimeActive = DateTime.Now;
                                    sleep = false;

                                    Byte[] sendBytes = Encoding.ASCII.GetBytes(outData);
                                    try
                                    {
                                        _serialPort.Write(sendBytes, 0, sendBytes.Length);
                                        TransmittedData?.Invoke(_guid);
                                    }
                                    catch (Exception ex) when (ex is IOException || ex is InvalidOperationException)
                                    {
                                        break;
                                    }
                                }
                            }
                        }

                        if ((DateTime.Now - lastTimeActive).TotalMilliseconds > 5000 && _serialPort.IsOpen)
                        {
                            Byte[] sendBytes = Encoding.ASCII.GetBytes("\0");
                            try
                            {
                                _serialPort.Write(sendBytes, 0, sendBytes.Length);
                                lastTimeActive = DateTime.Now;
                            }
                            catch (Exception ex) when (ex is IOException || ex is InvalidOperationException)
                            {
                                break;
                            }
                        }

                        if (sleep)
                            Thread.Sleep(50);
                        else
                            Thread.Sleep(1);
                    }
                }
                catch (Exception ex) when (ex is IOException || ex is InvalidOperationException)
                {
                    // Handle connection issues
                }
                catch (Exception ex)
                {
                    Debug.Print($">>>>>>   Exception in serial port {_guid}: {ex.Message}");
                }
                finally
                {
                    if (_serialPort != null && _serialPort.IsOpen)
                    {
                        _serialPort.Close();
                    }
                }
            }

            public static List<string> GetAvailableComPorts()
            {
                return new List<string>(SerialPort.GetPortNames());
            }
        }

        // Each startlisten will only accept a single TCP client for that IP/port combo.
        // UDP startlisteners can have messages from multiple UDP clients, but single endpoint for output
        public static event Action<Guid> ClientConnected;
        public static event Action<Guid> ClientDisconnected;
        public static event Action<Guid, string> ReceivedDataString;
        public static event Action<Guid> TransmittedData;
        public static event Action<Guid, MMIOType, bool> ConnectorRunning;

        private static ConcurrentDictionary<Guid, UdpListener> _udp_listeners;
        private static ConcurrentDictionary<Guid, TcpListener> _tcpip_listeners;
        private static ConcurrentDictionary<Guid, TcpClientHandler> _tcpip_clients;
        private static ConcurrentDictionary<Guid, SerialPortHandler> _serial_ports;
        private static ConcurrentDictionary<Guid, clsMMIO> _mmio_data;

        private static readonly object _connectionLock = new object();

        static MultiMeterIO()
        {
            Debug.Print("MultiMeterIO Init");
            _udp_listeners = new ConcurrentDictionary<Guid, UdpListener>();
            _tcpip_listeners = new ConcurrentDictionary<Guid, TcpListener>();
            _tcpip_clients = new ConcurrentDictionary<Guid, TcpClientHandler>();
            _serial_ports = new ConcurrentDictionary<Guid, SerialPortHandler>();
            _mmio_data = new ConcurrentDictionary<Guid, clsMMIO>();

            ReceivedDataString += MultiMeterIO_ReceivedDataString;
            TransmittedData += MultiMeterIO_TransmittedData;
            ConnectorRunning += MultiMeterIO_ListenerRunning;
        }
        public static ConcurrentDictionary<Guid, clsMMIO> Data
        {
            get { return _mmio_data; }
        }
        public static bool StartListeningUDP(clsMMIO mmio)
        {
            if (mmio == null) return false;
            if (mmio.Type != MMIOType.UDP_LISTENER) return false;
            if (!Common.IsIpv4Valid(mmio.IP, mmio.Port)) return false;

            UdpListener listener;
            try
            {
                listener = new UdpListener(mmio.Guid, mmio.Type, mmio.IP, mmio.Port);
            }
            catch
            {
                return false;
            }
            listener.ReceivedDataString += (listenerGuid, data) => ReceivedDataString?.Invoke(listenerGuid, data);
            listener.ConnectorRunning += (listenerGuid, type, running) => ConnectorRunning?.Invoke(listenerGuid, type, running);
            listener.TransmittedData += (guid) => TransmittedData?.Invoke(guid);

            if (_udp_listeners.TryAdd(mmio.Guid, listener))
            {
                listener.Start();
                return true;
            }

            return false;
        }

        public static bool StartListeningTCPIP(clsMMIO mmio)
        {
            if (mmio == null) return false;
            if (mmio.Type != MMIOType.TCPIP_LISTENER) return false;
            if (!Common.IsIpv4Valid(mmio.IP, mmio.Port)) return false;

            TcpListener listener;
            try
            {
                listener = new TcpListener(mmio.Guid, mmio.Type, mmio.IP, mmio.Port);
            }
            catch
            {
                return false;
            }
            listener.ReceivedDataString += (listenerGuid, data) => ReceivedDataString?.Invoke(listenerGuid, data);
            listener.ConnectorRunning += (listenerGuid, type, running) => ConnectorRunning?.Invoke(listenerGuid, type, running);
            listener.TransmittedData += (guid) => TransmittedData?.Invoke(guid);

            if (_tcpip_listeners.TryAdd(mmio.Guid, listener))
            {
                listener.Start();
                return true;
            }
            return false;
        }
        public static bool StartTcpClient(clsMMIO mmio)
        {
            if (mmio == null) return false;
            if (mmio.Type != MMIOType.TCPIP_CLIENT) return false;
            if (!Common.IsIpv4Valid(mmio.IP, mmio.Port)) return false;

            TcpClientHandler clientHandler;
            try
            {
                clientHandler = new TcpClientHandler(mmio.Guid, mmio.Type, mmio.IP, mmio.Port);
            }
            catch
            {
                return false;
            }

            clientHandler.ReceivedDataString += (clientGuid, data) => ReceivedDataString?.Invoke(clientGuid, data);
            clientHandler.ConnectorRunning += (clientGuid, type, running) => ConnectorRunning?.Invoke(clientGuid, type, running);
            clientHandler.TransmittedData += (guid) => TransmittedData?.Invoke(guid);

            if (_tcpip_clients.TryAdd(mmio.Guid, clientHandler))
            {
                clientHandler.Start();
                return true;
            }

            return false;
        }
        public static bool StartSerialPort(clsMMIO mmio)
        {
            if (mmio == null) return false;
            if (mmio.Type != MMIOType.SERIAL) return false;
            if (string.IsNullOrEmpty(mmio.ComPort) || mmio.BaudRate <= 0 || mmio.DataBits <= 0) return false;

            SerialPortHandler serialPortHandler;
            try
            {
                serialPortHandler = new SerialPortHandler(mmio.Guid, mmio.Type, mmio.ComPort, mmio.BaudRate, mmio.DataBits, mmio.StopBits, mmio.Parity);
            }
            catch
            {
                return false;
            }

            serialPortHandler.ReceivedDataString += (portGuid, data) => ReceivedDataString?.Invoke(portGuid, data);
            serialPortHandler.ConnectorRunning += (portGuid, type, running) => ConnectorRunning?.Invoke(portGuid, type, running);
            serialPortHandler.TransmittedData += (guid) => TransmittedData?.Invoke(guid);

            if (_serial_ports.TryAdd(mmio.Guid, serialPortHandler))
            {
                serialPortHandler.Start();
                return true;
            }

            return false;
        }
        public static void StopConnection(Guid guid)
        {
            UdpListener listener;
            if (_udp_listeners.TryRemove(guid, out listener))
            {
                listener.Stop();
            }
            TcpListener tcpipListener;
            if (_tcpip_listeners.TryRemove(guid, out tcpipListener))
            {
                tcpipListener.Stop();
            }
            TcpClientHandler tcpipClient;
            if (_tcpip_clients.TryRemove(guid, out tcpipClient))
            {
                tcpipClient.Stop();
            }
            SerialPortHandler serialPort;
            if (_serial_ports.TryRemove(guid, out serialPort))
            {
                serialPort.Stop();
            }

            Debug.Print("MultiMeterIO Stopped listening : " + guid.ToString());        
        }
        public static void StopConnections()
        {
            foreach (KeyValuePair<Guid, UdpListener> kvp in _udp_listeners)
            {
                kvp.Value.Stop();
            }
            _udp_listeners.Clear();

            foreach (KeyValuePair<Guid, TcpListener> kvp in _tcpip_listeners)
            {
                kvp.Value.Stop();
            }
            _tcpip_listeners.Clear();

            foreach (KeyValuePair<Guid, TcpClientHandler> kvp in _tcpip_clients)
            {
                kvp.Value.Stop();
            }
            _tcpip_clients.Clear();

            foreach (KeyValuePair<Guid, SerialPortHandler> kvp in _serial_ports)
            {
                kvp.Value.Stop();
            }
            _serial_ports.Clear();
        }
        public static bool AlreadyConfigured(string ip, int port, MMIOType type)
        {
            foreach (KeyValuePair<Guid, clsMMIO> kvp in _mmio_data)
            {
                clsMMIO mmio = kvp.Value;
                if (mmio.IP.Equals(ip) &&
                    mmio.Port == port && mmio.Type == type)
                {
                    return true;
                }
            }
            return false;
        }
        public static string FourChar(string data1, int data2, Guid guid)
        {
            string input = $"{data1}:{data2}:{guid}";
            using (SHA256 sha256 = SHA256.Create())
            {
                byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
                string base64Hash = Convert.ToBase64String(hashBytes);
                return convertToFourChar(base64Hash);
            }
        }

        private static string convertToFourChar(string base64Hash)
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            char[] result = new char[4];
            int[] indices = new int[4];
            for (int i = 0; i < base64Hash.Length; i++)
            {
                indices[i % 4] = (indices[i % 4] + base64Hash[i]) % chars.Length;
            }
            for (int i = 0; i < 4; i++)
            {
                result[i] = chars[indices[i]];
            }
            return new string(result);
        }

        public static string GetSaveData()
        {
            //1 for version 1
            string tmp = Common.SerializeToBase64<ConcurrentDictionary<Guid, clsMMIO>>(_mmio_data);
            tmp = tmp.Replace("/", "[backslash]");
            return "1|" + tmp;

            //string data = "";

            //data += _mmio_data.Count.ToString() + "|";
            //foreach (KeyValuePair<Guid, clsMMIO> kvp in _mmio_data)
            //{
            //    clsMMIO mmio = kvp.Value;
            //    data += mmio.Guid.ToString() + "|"; //1
            //    data += mmio.Direction.ToString() + "|"; //2
            //    data += mmio.IP + "|"; //3
            //    data += mmio.Port.ToString() + "|"; //4
            //    data += mmio.FormatIn.ToString() + "|"; //5
            //    data += mmio.Type.ToString() + "|"; //6
            //    data += mmio.Enabled.ToString() + "|"; //7
            //    data += mmio.TerminatorIn.ToString() + "|"; //8
            //    data += mmio.FormatOut.ToString() + "|"; //9
            //    data += mmio.TerminatorOut.ToString() + "|"; //10
            //    data += mmio.CustomTerminatorIn.Replace("|", "++><++") + "|"; //11
            //    data += mmio.CustomTerminatorOut.Replace("|", "++><++") + "|"; //12
            //    data += mmio.UdpEndpointIP + "|"; //13
            //    data += mmio.UdpEndpointPort.ToString() + "|"; //14

            //    // always last
            //    data += mmio.Variables().Count.ToString() + "|"; //15
            //    foreach(KeyValuePair<string, object> kvpvar in mmio.Variables())
            //    {
            //        data += kvpvar.Key + "|";

            //        string val;
            //        object valobj = kvpvar.Value;

            //        if (valobj is int)
            //            val = valobj.ToString();
            //        else if (valobj is float)
            //            val = ((float)valobj).ToString("0.0#####");
            //        else if (valobj is double)
            //            val = ((double)valobj).ToString("0.0#####");
            //        else if (valobj is bool)
            //            val = valobj.ToString().ToLower();
            //        else
            //            val = valobj.ToString().Replace("|", "++><++");

            //        data += val + "|";
            //    }
            //}
            //data = data.Substring(0, data.Length - 1); // scrap trailing |

            //return data;
        }
        public static bool RestoreSaveData2(string data)
        {
            if (string.IsNullOrEmpty(data)) return true;
            try
            {
                StopConnections(); //start new                

                string[] parts = data.Split('|');
                if (parts.Length != 2) return false;

                if (parts[0] == "1") // 1 signifies version 1 of the serialize for future proofing
                {
                    string tmp = parts[1].Replace("[backslash]", "/");
                    _mmio_data = Common.DeserializeFromBase64<ConcurrentDictionary<Guid, clsMMIO>>(tmp);
                }

                foreach(KeyValuePair<Guid, clsMMIO> kvp in _mmio_data)
                {
                    clsMMIO mmio = kvp.Value;
                    if (mmio.Enabled)
                        mmio.StartConnection();
                }

                return true;
            }
            catch
            {
                return false;
            }
        }
        public static bool RestoreSaveData(string data)
        {
            if (string.IsNullOrEmpty(data)) return true;
            StopConnections(); //start new           

            string[] parts = data.Split('|');
            if (parts.Length < 1) return true;

            bool ok;
            int listeners;

            ok = int.TryParse(parts[0], out listeners);
            if (ok)
            {
                Guid guid = Guid.Empty;
                MMIODirection direction = MMIODirection.BOTH;
                MMIOFormat format_in = MMIOFormat.JSON;
                MMIOFormat format_out = MMIOFormat.JSON;
                MMIOType type = MMIOType.UDP_LISTENER;
                MMIOTerminator terminator_in = MMIOTerminator.NONE;
                MMIOTerminator terminator_out = MMIOTerminator.NONE;
                int port = 0;
                bool enabled = false;
                int variables = 0;
                int variable_count_index = 0;
                int udp_endpoint_port = 0;

                int idx = 0;

                for (int i = 0; i < listeners; i++)
                {
                    clsMMIO mmio = new clsMMIO();
                    if (ok)
                    {
                        ok = Guid.TryParse(parts[idx + 1], out guid);
                    }
                    if (ok)
                    {
                        mmio.Guid = guid;
                        ok = Enum.TryParse<MMIODirection>(parts[idx + 2], out direction);
                    }
                    if (ok)
                    {
                        mmio.Direction = direction;
                        mmio.IP = parts[idx + 3];
                        ok = int.TryParse(parts[idx + 4], out port);
                    }
                    if (ok)
                    {
                        mmio.Port = port;
                        ok = Enum.TryParse<MMIOFormat>(parts[idx + 5], out format_in);
                    }
                    if (ok)
                    {
                        mmio.FormatIn = format_in;
                        ok = Enum.TryParse<MMIOType>(parts[idx + 6], out type);
                    }
                    if (ok)
                    {
                        mmio.Type = type;
                        ok = bool.TryParse(parts[idx + 7], out enabled);
                    }
                    if (ok)
                    {
                        mmio.Enabled = enabled;
                        ok = Enum.TryParse<MMIOTerminator>(parts[idx + 8], out terminator_in);
                    }
                    if (ok)
                    {
                        mmio.TerminatorIn = terminator_in;
                        ok = Enum.TryParse<MMIOFormat>(parts[idx + 9], out format_out);
                    }
                    if (ok)
                    {
                        mmio.FormatOut = format_out;
                        ok = Enum.TryParse<MMIOTerminator>(parts[idx + 10], out terminator_out);
                    }
                    if (ok)
                    {
                        mmio.TerminatorOut = terminator_out;
                        mmio.CustomTerminatorIn = parts[idx + 11].Replace("++><++", "|");
                        mmio.CustomTerminatorOut = parts[idx + 12].Replace("++><++", "|");
                        mmio.UdpEndpointIP = parts[idx + 13];
                        ok = int.TryParse(parts[idx + 14], out udp_endpoint_port);
                    }
                    if (ok)
                    {
                        mmio.UdpEndpointPort = udp_endpoint_port;
                    }
                    if (ok)
                    {
                        // restore variables
                        variable_count_index = 15; // this needs to be one larger than N in the [idx + N] above
                        ok = int.TryParse(parts[idx + variable_count_index], out variables);
                        if (ok)
                        {
                            for(int n = 0; n < variables; n++)
                            {
                                string val = parts[idx + (variable_count_index + 2) + (n * 2)].Replace("++><++", "|");
                                Type tpe = mmio.DetermineType(val);
                                object typedValue = mmio.ConvertToType(val, tpe);
                                ok = mmio.Variables().TryAdd(parts[idx + (variable_count_index + 1) + (n * 2)], typedValue);
                                if (!ok) break;
                            }
                        }
                    }
                    if (ok)
                    {
                        ok = _mmio_data.TryAdd(guid, mmio);
                        if (!ok)
                        {
                            StopConnection(guid);
                            return false;
                        }
                    }
                    if (ok && enabled)
                    {
                        mmio.StartConnection();
                    }
                    if (ok)
                    {
                        idx += variable_count_index + (variables * 2);
                    }
                }
            }
            return ok;
        }
        public static bool AddMMIO(clsMMIO mmio)
        {
            if (_mmio_data.ContainsKey(mmio.Guid)) return false;
            if (mmio == null) return false;

            bool ok = _mmio_data.TryAdd(mmio.Guid, mmio);
            if (!ok)
            {
                StopConnection(mmio.Guid);
                return false;
            }
            return ok;
        }
        public static bool RemoveMMIO(Guid guid)
        {
            bool ok = false;
            if (_mmio_data.ContainsKey(guid))
            {
                ok = _mmio_data.TryRemove(guid, out clsMMIO mmio);
                if (ok) StopConnection(guid);
            }
            return ok;
        }
        public static void SendDataMMIO(Guid guid, string data)
        {
            if(_mmio_data.ContainsKey(guid))
            {
                clsMMIO mmio = _mmio_data[guid];
                mmio.EnqueueOutbound(data);
            }
        }
        public static Guid GuidfromFourChar(string fourChar)
        {
            foreach(KeyValuePair<Guid, clsMMIO> kvp in _mmio_data)
            {
                clsMMIO mmio = kvp.Value;
                if(mmio.FourChar == fourChar) return mmio.Guid;
            }
            return Guid.Empty;
        }
        private static void MultiMeterIO_ListenerRunning(Guid guid, MMIOType type, bool running)
        {
            if (!MultiMeterIO.Data.ContainsKey(guid)) return;
            MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[guid];
            mmio.Active = running;
        }
        private static void MultiMeterIO_TransmittedData(Guid guid)
        {
        }
        [DebuggerHidden]
        public static bool IsValidXml(string xmlString)
        {
            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.DtdProcessing = DtdProcessing.Parse;
                settings.XmlResolver = null;
                settings.ValidationType = ValidationType.None;

                using (XmlReader reader = XmlReader.Create(new System.IO.StringReader(xmlString), settings))
                {
                    while (reader.Read())
                    {
                        // Simply reading the XML, if it's invalid, an exception will be thrown
                    }
                }

                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }
        //[DebuggerHidden]
        private static void MultiMeterIO_ReceivedDataString(Guid guid, string dataString)
        {
            char[] charsToTrim = { ' ', '\n', '\r', '\t', '\0' };
            dataString = dataString.Trim(charsToTrim);
            if (string.IsNullOrWhiteSpace(dataString)) return;

            //Debug.Print("Data : " + guid + " : [" + dataString + "]");
            if (!MultiMeterIO.Data.ContainsKey(guid)) return;

            MultiMeterIO.clsMMIO mmio = MultiMeterIO.Data[guid];
            MultiMeterIO.MMIOFormat format = mmio.FormatIn;
            string fourChar = mmio.FourChar;

            Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
            switch (format)
            {
                case MultiMeterIO.MMIOFormat.JSON:
                    try
                    {
                        JObject parsedJson = JObject.Parse(dataString);
                        parseJsonToken(parsedJson, fourChar, keyValuePairs);
                    }
                    catch { }
                    break;
                case MultiMeterIO.MMIOFormat.XML:
                    try
                    {
                        if (IsValidXml(dataString))
                        {
                            XElement parsedXml = XElement.Parse(dataString);
                            parseXMLElement(parsedXml, fourChar, keyValuePairs);
                        }
                    }
                    catch { }
                    break;
                case MultiMeterIO.MMIOFormat.RAW:
                    try
                    {
                        string[] split = dataString.Split(':');
                        int pairs = split.Length / 2;
                        for(int n = 0;n < pairs; n++)
                        {
                            keyValuePairs.Add(fourChar + "." + split[n * 2], split[(n * 2) + 1]);
                        }
                    }
                    catch { }
                    break;
            }

            //foreach (KeyValuePair<string, string> kvp in keyValuePairs)
            //{
            //    //Debug.Print($"key = {kvp.Key}    value = {kvp.Value}    type = {determineType(kvp.Value)}");
            //    Type tpe = DetermineType(kvp.Value);
            //    object typedValue = ConvertToType(kvp.Value, tpe);
            //    mmio.SetVariable(kvp.Key, typedValue);
            //}
            Parallel.ForEach(keyValuePairs, kvp =>
            {
                Type tpe = mmio.DetermineType(kvp.Value);
                object typedValue = mmio.ConvertToType(kvp.Value, tpe);
                mmio.SetVariable(kvp.Key, typedValue);
            });
        }
        public static void parseJsonToken(JToken token, string currentPath, Dictionary<string, string> keyValuePairs)
        {
            if (token is JValue)
            {
                //keyValuePairs[currentPath] = ((JValue)token).ToString();

                if (token.Type == JTokenType.Integer ||
                    token.Type == JTokenType.Float ||
                    token.Type == JTokenType.Boolean
                    )
                {
                    keyValuePairs[currentPath] = ((JValue)token).ToString(Newtonsoft.Json.Formatting.None);
                }
                else
                {
                    keyValuePairs[currentPath] = ((JValue)token).ToString();
                }
            }
            else if (token is JObject)
            {
                JObject obj = (JObject)token;
                foreach (JProperty property in obj.Properties())
                {
                    parseJsonToken(property.Value, $"{currentPath}.{property.Name}", keyValuePairs);
                }
            }
            else if (token is JArray)
            {
                JArray array = (JArray)token;
                for (int i = 0; i < array.Count; i++)
                {
                    parseJsonToken(array[i], $"{currentPath}[{i}]", keyValuePairs);
                }
            }
        }
        private static void parseXMLElement(XElement element, string currentPath, Dictionary<string, string> keyValuePairs)
        {
            foreach (XAttribute attribute in element.Attributes())
            {
                keyValuePairs[$"{currentPath}.{attribute.Name}"] = attribute.Value;
            }

            bool hasElements = false;
            foreach (XElement childElement in element.Elements())
            {
                hasElements = true;
                parseXMLElement(childElement, $"{currentPath}.{childElement.Name}", keyValuePairs);
            }

            if (!hasElements)
            {
                keyValuePairs[currentPath] = element.Value;
            }
        }
    }
}
