/// <summary>
        /// Generates the list of parameter controls.
        /// </summary>
        /// <remarks>
        /// We need to get the list of parameters from the VisGen plugin, then for each
        /// parameter we need to merge the value from the Visualization's value list.
        /// If we don't find a corresponding entry in the Visualization, we use the
        /// default value.
        /// </remarks>
        private void GenerateParamControls(VisDescr descr)
        {
            VisParamDescr[] paramDescrs = descr.VisParamDescrs;

            ParameterList.Clear();
            foreach (VisParamDescr vpd in paramDescrs)
            {
                string rangeStr   = string.Empty;
                object defaultVal = vpd.DefaultValue;

                // If we're editing a visualization, use the values from that as default.
                if (mOrigVis != null)
                {
                    if (mOrigVis.VisGenParams.TryGetValue(vpd.Name, out object val))
                    {
                        // Do we need to confirm that val has the correct type?
                        defaultVal = val;
                    }
                }
                else
                {
                    // New visualization.  Use the set's offset as the default value for
                    // any parameter called "offset".  Otherwise try to pull a value with
                    // the same name out of the last thing we edited.
                    if (vpd.Name.ToLowerInvariant() == "offset")
                    {
                        defaultVal = mSetOffset;
                    }
                    else if (sLastParams.TryGetValue(vpd.Name, out object value))
                    {
                        defaultVal = value;
                    }
                }

                // Set up rangeStr, if appropriate.
                VisParamDescr altVpd = vpd;
                if (vpd.CsType == typeof(int) || vpd.CsType == typeof(float))
                {
                    if (vpd.Special == VisParamDescr.SpecialMode.Offset)
                    {
                        defaultVal = mFormatter.FormatOffset24((int)defaultVal);
                        rangeStr   = "[" + mFormatter.FormatOffset24(0) + "," +
                                     mFormatter.FormatOffset24(mProject.FileDataLength - 1) + "]";

                        // Replace the vpd to provide a different min/max.
                        altVpd = new VisParamDescr(vpd.UiLabel, vpd.Name, vpd.CsType,
                                                   0, mProject.FileDataLength - 1, vpd.Special, vpd.DefaultValue);
                    }
                    else
                    {
                        rangeStr = "[" + vpd.Min + "," + vpd.Max + "]";
                    }
                }

                ParameterValue pv = new ParameterValue(altVpd, defaultVal, rangeStr);

                ParameterList.Add(pv);
            }
        }
        public ParameterValue(VisParamDescr vpd, object val, string rangeText)
        {
            Descr     = vpd;
            Value     = val;
            RangeText = rangeText;

            char labelSuffix = (vpd.CsType == typeof(bool)) ? '?' : ':';

            UiString = vpd.UiLabel + labelSuffix;
        }