using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Linq;
using System.Windows.Forms;
using SCJMapper_V2.SC;
using SCJMapper_V2.Devices.Mouse;
using SCJMapper_V2.Devices.Keyboard;
using System.Collections;
namespace SCJMapper_V2.Devices.Options
{
///
/// Maintains Options - something like:
///
///
///
///
///
///
///
///
///
///
/// [type] : set to shared, mouse, xboxpad, or joystick (NOTE the CIG type used is keyboard .. for the mouse....(Grrr) - we use MOUSE
/// [instance] : set to the device number; js1=1, js2=2, etc
/// [optiongroup] : set to what group the option should affect (for available groups see default actionmap)
/// [option] : instance, sensitivity, exponent, nonlinearity *instance is a bug that will be fixed to 'invert' in the future
/// [value] : for invert use 0/1; for others use 0.0 to 2.0
///
/// options are given per deviceClass and instance - it seems
///
public class OptionTree : ICloneable
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod( ).DeclaringType );
// Support only one set of independent options (string storage)
List m_stringOptions = new List( );
#region Clonable support
// bag for all tuning items - key is the option name
CloneableDictionary m_tuning = new CloneableDictionary( );
///
/// Clone this object
///
/// A deep Clone of this object
public object Clone()
{
var ot = (OptionTree)this.MemberwiseClone( );
// more objects to deep copy
ot.m_stringOptions = new List( m_stringOptions );
if ( this.m_tuning != null ) ot.m_tuning = (CloneableDictionary)this.m_tuning.Clone( );
return ot;
}
///
/// Check clone against This
///
///
/// True if the clone is identical but not a shallow copy
internal bool CheckClone( OptionTree clone )
{
bool ret = true;
// object vars first
ret &= ( !object.ReferenceEquals( this.m_stringOptions, clone.m_stringOptions ) ); // shall not be the same object !!
// check m_tuning Dictionary
ret &= ( this.m_tuning.Count == clone.m_tuning.Count );
if ( ret ) {
for ( int i = 0; i < this.m_tuning.Count; i++ ) {
ret &= ( this.m_tuning.ElementAt( i ).Key == clone.m_tuning.ElementAt( i ).Key );
ret &= ( !object.ReferenceEquals( this.m_tuning.ElementAt( i ).Value, clone.m_tuning.ElementAt( i ).Value ) ); // shall not be the same object !!
ret &= ( this.m_tuning.ElementAt( i ).Value.CheckClone( clone.m_tuning.ElementAt( i ).Value ) );
}
}
return ret;
}
#endregion
// ctor
public OptionTree( DeviceCls device )
{
if ( m_profileOptions.Count( ) == 0 ) {
log.Error( "cTor OptionTree - profile not yet read" );
}
// get options for the device class
var devOpts = m_profileOptions.Where( x => x.DeviceClass == device.DevClass );
foreach (ProfileOptionRec rec in devOpts ) {
m_tuning.Add( rec.OptName, new DeviceTuningParameter( rec.OptName, device ) );
}
}
public int Count
{
get { return ( m_stringOptions.Count + 1 ); }
}
public void ResetDynamicItems()
{
foreach ( KeyValuePair kv in m_tuning ) {
DeviceTuningParameter item = kv.Value;
if ( item != null ) {
item.ResetDynamicItems( );
}
}
}
// provide access to Tuning items
///
/// Returns a tuning item for the asked option
///
/// The option to get
/// A DeviceTuning item or null if it does not exist
public DeviceTuningParameter TuningItem( string optionName )
{
if ( m_tuning.ContainsKey( optionName ) )
return m_tuning[optionName];
else
return null;
}
private string[] FormatXml( string xml )
{
try {
var doc = XDocument.Parse( xml );
return doc.ToString( ).Split( new string[] { string.Format( "\n" ) }, StringSplitOptions.RemoveEmptyEntries );
}
catch ( Exception ) {
return new string[] { xml };
}
}
///
/// Dump the Options as partial XML nicely formatted
///
/// the action as XML fragment
public string toXML()
{
string r = "";
// and dump the contents of plain string options
foreach ( string x in m_stringOptions ) {
if ( !string.IsNullOrWhiteSpace( x ) ) {
foreach ( string line in FormatXml( x ) ) {
r += string.Format( "\t{0}", line );
}
}
r += string.Format( "\n" );
}
// dump Tuning
foreach ( KeyValuePair kv in m_tuning ) {
r += kv.Value.Options_toXML( );
}
return r;
}
///
/// Read an Options from XML - do some sanity check
///
/// the XML action fragment
/// True if an action was decoded
public bool fromXML( XElement options )
{
/*
* This can be a lot of the following options
* try to do our best....
*
*
*
*/
string instance = (string)options.Attribute( "instance" ); // mandadory
string type = (string)options.Attribute( "type" ); // mandadory
if ( !int.TryParse( instance, out int nInstance ) ) nInstance = 0; // get the one from the map if given (else it's a map error...)
string productS = (string)options.Attribute( "Product" ); // optional 3.5 ??
string devClass = DeviceCls.DeviceClass; // the generic one
if ( !string.IsNullOrEmpty(type)) devClass = type; // get the one from the map if given (else it's a map error...)
// mouse arrives as type keyboard - fix it to deviceClass mouse here
if ( KeyboardCls.IsDeviceClass( devClass ) )
devClass = MouseCls.DeviceClass;
// check if the profile contains the one we found in the map - then parse the element
foreach ( XElement item in options.Elements( ) ) {
if ( m_profileOptions.Contains( new ProfileOptionRec( ) { DeviceClass = devClass, OptName = item.Name.LocalName } ) ) {
m_tuning[item.Name.LocalName].Options_fromXML( item, devClass, int.Parse( instance ) );
}
}
return true;
}
#region static - Profile Handling
public class ProfileOptionRec : IEquatable
{
public string DeviceClass { get; set; }
public string OptGroup { get; set; }
public string OptName { get; set; }
public bool ShowCurve { get; set; }
public bool ShowInvert { get; set; }
public ProfileOptionRec()
{
DeviceClass = DeviceCls.DeviceClass; OptGroup = ""; OptName = ""; ShowCurve = false; ShowInvert = false;
}
// same class and name means records match
public bool Equals( ProfileOptionRec other )
{
return ( ( this.DeviceClass == other.DeviceClass ) && ( this.OptName == other.OptName ) );
}
}
private static List m_profileOptions = new List( );
///
/// Returns a list of ProfileOptions found in the defaultProfile
///
public static IList ProfileOptions { get => m_profileOptions; }
///
/// Clears the stored optiontree items from the profile
/// must be cleared before re-reading them from profile
///
public static void InitOptionReader()
{
m_profileOptions = new List( );
}
///
/// Reads optiongroup nodes
/// the tree is composed of such nodes - we dive recursively
///
/// The optiongroup to parse
/// The deviceclass it belongs to
/// True if OK
private static bool ReadOptiongroup( XElement optiongroupIn, string devClass, string optGroup )
{
bool retVal = true;
// collect content and process further groups
string name = (string)optiongroupIn.Attribute( "name" );
string uiLabel = (string)optiongroupIn.Attribute( "UILabel" );
if ( string.IsNullOrEmpty( uiLabel ) )
uiLabel = name; // subst if not found in Action node
SCUiText.Instance.Add( name, uiLabel ); // Register item for translation
// further groups
IEnumerable optiongroups = from x in optiongroupIn.Elements( )
where ( x.Name == "optiongroup" )
select x;
foreach ( XElement optiongroup in optiongroups ) {
retVal &= ReadOptiongroup( optiongroup, devClass, name ); // current is the group if we dive one down
}
// murks.. determine if it is a terminal node, then get items
if ( optiongroups.Count() == 0 ) {
ProfileOptionRec optRec = new ProfileOptionRec { DeviceClass = devClass, OptGroup=optGroup, OptName = name }; // create a new one
// override props if they arrive in the node
string attr = (string)optiongroupIn.Attribute( "UIShowCurve" );
if ( !string.IsNullOrEmpty( attr ) ) {
if ( int.TryParse( attr, out int showCurve ) ) {
optRec.ShowCurve = ( showCurve == 1 );
}
}
attr = (string)optiongroupIn.Attribute( "UIShowInvert" );
if ( !string.IsNullOrEmpty( attr ) ) {
if ( int.TryParse( attr, out int showInvert ) ) {
optRec.ShowInvert = ( showInvert == 1 );
}
}
if ( optRec.ShowCurve || optRec.ShowInvert)
m_profileOptions.Add( optRec ); // add only if something is to tweak..
}
return retVal;
}
///
/// Collects items from the profile optiontree
///
/// The optiontree node
/// True if OK
public static bool fromProfileXML( XElement optiontree )
{
/*
//
//
//
*/
log.Debug( "fromProfileXML - Entry" );
bool retVal = true;
string devClass = (string)optiontree.Attribute( "type" );
// mouse arrives as type keyboard - fix it to deviceClass mouse here
if ( KeyboardCls.IsDeviceClass( devClass ) )
devClass = MouseCls.DeviceClass;
IEnumerable optiongroups = from x in optiontree.Elements( )
where ( x.Name == "optiongroup" )
select x;
foreach ( XElement optiongroup in optiongroups ) {
retVal &= ReadOptiongroup( optiongroup, devClass, "master" );
}
return retVal;
}
#endregion
}
}