<commit_before>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NLog;
using NLog.Fluent;
using VRage.Game;
using VRage.Serialization;

namespace Torch.Views
{
    /// <summary>
    /// Interaction logic for PropertyGrid.xaml
    /// </summary>
    public partial class PropertyGrid : UserControl
    {
        private Dictionary<Type, Grid> _viewCache = new Dictionary<Type, Grid>();

        public static readonly DependencyProperty IgnoreDisplayProperty = DependencyProperty.Register("IgnoreDisplay", typeof(bool), typeof(PropertyGrid));

        public bool IgnoreDisplay
        {
            get => (bool)base.GetValue(IgnoreDisplayProperty);
            set => base.SetValue(IgnoreDisplayProperty, value);
        }

        public PropertyGrid()
        {
            InitializeComponent();
            DataContextChanged += OnDataContextChanged;
        }

        private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
            {
                ScrollViewer.Content = null;
                TbFilter.IsEnabled = false;
                return;
            }

            var content = GenerateForType(e.NewValue.GetType());
            content.DataContext = e.NewValue;
            ScrollViewer.Content = content;
            TbFilter.IsEnabled = true;
        }

        public Grid GenerateForType(Type t)
        {
            if (_viewCache.TryGetValue(t, out var v))
                return v;

            var properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);

            var grid = new Grid();
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) });

            var categories = new Dictionary<string, List<PropertyInfo>>();
            var descriptors = new Dictionary<PropertyInfo, DisplayAttribute>(properties.Length);

            foreach (var property in properties)
            {
                //Attempt to load our custom DisplayAttribute
                var a = property.GetCustomAttribute<DisplayAttribute>();
                //If not found and IgnoreDisplay is not set, fall back to system DisplayAttribute
                if (a == null && !IgnoreDisplay)
                    a = property.GetCustomAttribute<System.ComponentModel.DataAnnotations.DisplayAttribute>();
                if (a?.Visible == false)
                    continue;
                descriptors[property] = a;
                string category = a?.GroupName ?? "Misc";

                if (!categories.TryGetValue(category, out List<PropertyInfo> l))
                {
                    l = new List<PropertyInfo>();
                    categories[category] = l;
                }
                l.Add(property);
            }

            var curRow = 0;
            foreach (var c in categories.OrderBy(x => x.Key))
            {
                grid.RowDefinitions.Add(new RowDefinition());
                var cl = new TextBlock
                         {
                             Text = c.Key,
                             VerticalAlignment = VerticalAlignment.Center
                         };
                cl.SetValue(Grid.ColumnProperty, 0);
                cl.SetValue(Grid.ColumnSpanProperty, 2);
                cl.SetValue(Grid.RowProperty, curRow);
                cl.Margin = new Thickness(3);
                cl.FontWeight = FontWeights.Bold;
                grid.Children.Add(cl);
                curRow++;

                c.Value.Sort((a, b) =>
                             {
                                 var c1 = descriptors[a]?.Order.CompareTo(descriptors[b]?.Order);
                                 if (c1.HasValue && c1.Value != 0)
                                     return c1.Value;
                                 return string.Compare((descriptors[a]?.Name ?? a.Name), descriptors[b]?.Name ?? b.Name, StringComparison.Ordinal);
                             });

                foreach (var property in c.Value)
                {
                    if (property.GetGetMethod() == null)
                        continue;

                    grid.RowDefinitions.Add(new RowDefinition());

                    var descriptor = descriptors[property];
                    var displayName = descriptor?.Name;
                    var propertyType = property.PropertyType;

                    var text = new TextBlock
                               {
                                   Text = displayName ?? property.Name,
                                   ToolTip = descriptor?.ToolTip ?? displayName,
                                   VerticalAlignment = VerticalAlignment.Center
                               };
                    text.SetValue(Grid.ColumnProperty, 0);
                    text.SetValue(Grid.RowProperty, curRow);
                    text.Margin = new Thickness(3);
                    text.Tag = $"{text.Text}: {descriptor?.Description}";
                    text.IsMouseDirectlyOverChanged += Text_IsMouseDirectlyOverChanged;
                    //if (descriptor?.Enabled == false)
                    //    text.IsEnabled = false;
                    grid.Children.Add(text);

                    FrameworkElement valueControl;
                    if (descriptor?.EditorType != null)
                    {
                        valueControl = (FrameworkElement)Activator.CreateInstance(descriptor.EditorType);
                        valueControl.SetBinding(FrameworkElement.DataContextProperty, property.Name);
                    }
                    else if (property.GetSetMethod() == null || descriptor?.ReadOnly == true)
                    {
                        valueControl = new TextBlock();
                        var binding = new Binding(property.Name)
                                      {
                                          Mode = BindingMode.OneWay
                                      };
                        valueControl.SetBinding(TextBlock.TextProperty, binding);
                    }
                    else if (propertyType == typeof(bool) || propertyType == typeof(bool?))
                    {
                        valueControl = new CheckBox();
                        valueControl.SetBinding(CheckBox.IsCheckedProperty, property.Name);
                    }
                    else if (propertyType.IsEnum)
                    {
                        valueControl = new ComboBox
                                       {<commit_msg>Make get-only collections editable in propertygrid<commit_after>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NLog;
using NLog.Fluent;
using VRage.Game;
using VRage.Serialization;

namespace Torch.Views
{
    /// <summary>
    /// Interaction logic for PropertyGrid.xaml
    /// </summary>
    public partial class PropertyGrid : UserControl
    {
        private Dictionary<Type, Grid> _viewCache = new Dictionary<Type, Grid>();

        public static readonly DependencyProperty IgnoreDisplayProperty = DependencyProperty.Register("IgnoreDisplay", typeof(bool), typeof(PropertyGrid));

        public bool IgnoreDisplay
        {
            get => (bool)base.GetValue(IgnoreDisplayProperty);
            set => base.SetValue(IgnoreDisplayProperty, value);
        }

        public PropertyGrid()
        {
            InitializeComponent();
            DataContextChanged += OnDataContextChanged;
        }

        private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
            {
                ScrollViewer.Content = null;
                TbFilter.IsEnabled = false;
                return;
            }

            var content = GenerateForType(e.NewValue.GetType());
            content.DataContext = e.NewValue;
            ScrollViewer.Content = content;
            TbFilter.IsEnabled = true;
        }

        public Grid GenerateForType(Type t)
        {
            if (_viewCache.TryGetValue(t, out var v))
                return v;

            var properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);

            var grid = new Grid();
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) });

            var categories = new Dictionary<string, List<PropertyInfo>>();
            var descriptors = new Dictionary<PropertyInfo, DisplayAttribute>(properties.Length);

            foreach (var property in properties)
            {
                //Attempt to load our custom DisplayAttribute
                var a = property.GetCustomAttribute<DisplayAttribute>();
                //If not found and IgnoreDisplay is not set, fall back to system DisplayAttribute
                if (a == null && !IgnoreDisplay)
                    a = property.GetCustomAttribute<System.ComponentModel.DataAnnotations.DisplayAttribute>();
                if (a?.Visible == false)
                    continue;
                descriptors[property] = a;
                string category = a?.GroupName ?? "Misc";

                if (!categories.TryGetValue(category, out List<PropertyInfo> l))
                {
                    l = new List<PropertyInfo>();
                    categories[category] = l;
                }
                l.Add(property);
            }

            var curRow = 0;
            foreach (var c in categories.OrderBy(x => x.Key))
            {
                grid.RowDefinitions.Add(new RowDefinition());
                var cl = new TextBlock
                         {
                             Text = c.Key,
                             VerticalAlignment = VerticalAlignment.Center
                         };
                cl.SetValue(Grid.ColumnProperty, 0);
                cl.SetValue(Grid.ColumnSpanProperty, 2);
                cl.SetValue(Grid.RowProperty, curRow);
                cl.Margin = new Thickness(3);
                cl.FontWeight = FontWeights.Bold;
                grid.Children.Add(cl);
                curRow++;

                c.Value.Sort((a, b) =>
                             {
                                 var c1 = descriptors[a]?.Order.CompareTo(descriptors[b]?.Order);
                                 if (c1.HasValue && c1.Value != 0)
                                     return c1.Value;
                                 return string.Compare((descriptors[a]?.Name ?? a.Name), descriptors[b]?.Name ?? b.Name, StringComparison.Ordinal);
                             });

                foreach (var property in c.Value)
                {
                    if (property.GetGetMethod() == null)
                        continue;

                    grid.RowDefinitions.Add(new RowDefinition());

                    var descriptor = descriptors[property];
                    var displayName = descriptor?.Name;
                    var propertyType = property.PropertyType;

                    var text = new TextBlock
                               {
                                   Text = displayName ?? property.Name,
                                   ToolTip = descriptor?.ToolTip ?? displayName,
                                   VerticalAlignment = VerticalAlignment.Center
                               };
                    text.SetValue(Grid.ColumnProperty, 0);
                    text.SetValue(Grid.RowProperty, curRow);
                    text.Margin = new Thickness(3);
                    text.Tag = $"{text.Text}: {descriptor?.Description}";
                    text.IsMouseDirectlyOverChanged += Text_IsMouseDirectlyOverChanged;
                    //if (descriptor?.Enabled == false)
                    //    text.IsEnabled = false;
                    grid.Children.Add(text);

                    FrameworkElement valueControl;
                    if (descriptor?.EditorType != null)
                    {
                        valueControl = (FrameworkElement)Activator.CreateInstance(descriptor.EditorType);
                        valueControl.SetBinding(FrameworkElement.DataContextProperty, property.Name);
                    }
                    else if (property.GetSetMethod() == null && !(propertyType.IsGenericType && typeof(ICollection).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))|| descriptor?.ReadOnly == true)
                    {
                        valueControl = new TextBlock();
                        var binding = new Binding(property.Name)
                                      {
                                          Mode = BindingMode.OneWay
                                      };
                        valueControl.SetBinding(TextBlock.TextProperty, binding);
                    }
                    else if (propertyType == typeof(bool) || propertyType == typeof(bool?))
                    {
                        valueControl = new CheckBox();
                        valueControl.SetBinding(CheckBox.IsCheckedProperty, property.Name);
                    }
                    else if (propertyType.IsEnum)
                    {
                        valueControl = new ComboBox
                                       {
{'fraction_non_alphanumeric': 0.08691706227310807, 'fraction_numerical': 0.0018151354370287628, 'mean_word_length': 1.4499743458183685, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '341158', 'n_tokens_mistral': 3339, 'n_tokens_neox': 3106, 'n_words': 697}