Skip to content

Commit

Permalink
Merge pull request #3252 from tom-englert/dev/#3251
Browse files Browse the repository at this point in the history
Fixes #3251: Decompiler Settings: Checkbox in group header does not reflect state of the group
  • Loading branch information
siegfriedpammer authored Aug 8, 2024
2 parents 7e74de2 + 1520b41 commit fa0ab07
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 150 deletions.
95 changes: 35 additions & 60 deletions ILSpy/Options/DecompilerSettingsPanel.xaml
Original file line number Diff line number Diff line change
@@ -1,63 +1,38 @@
<UserControl x:Class="ICSharpCode.ILSpy.Options.DecompilerSettingsPanel"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
xmlns:toms="urn:TomsToolbox">
<UserControl.Resources>
<CollectionViewSource x:Key="SettingsCollection" Source="{Binding Settings}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Margin="3" Grid.ColumnSpan="3" TextWrapping="Wrap" Text="{x:Static properties:Resources.DecompilerSettingsPanelLongText}" />
<ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource SettingsCollection}}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}" BasedOn="{StaticResource {x:Type GroupItem}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Padding="0" BorderThickness="0" IsExpanded="True">
<Expander.Header>
<CheckBox Checked="OnGroupChecked" Unchecked="OnGroupUnchecked" Loaded="OnGroupLoaded" VerticalContentAlignment="Center"
FontSize="16" FontWeight="Bold" Content="{Binding Name}" />
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Margin="19,0,0,0" IsChecked="{Binding IsEnabled}" Content="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options"
d:DataContext="{d:DesignInstance options:DecompilerSettingsViewModel}">
<DockPanel>
<TextBlock Margin="3" DockPanel.Dock="Top" TextWrapping="Wrap" Text="{x:Static properties:Resources.DecompilerSettingsPanelLongText}" />
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Settings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Expander Padding="0" BorderThickness="0" IsExpanded="True">
<Expander.Header>
<CheckBox VerticalContentAlignment="Center"
FontSize="16" FontWeight="Bold"
Content="{Binding Category}"
IsChecked="{Binding AreAllItemsChecked, Mode=TwoWay}"/>
</Expander.Header>
<ItemsControl ItemsSource="{Binding Settings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="28,0,0,0" IsChecked="{Binding IsEnabled}" Content="{Binding Description}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</UserControl>
59 changes: 2 additions & 57 deletions ILSpy/Options/DecompilerSettingsPanel.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Xml.Linq;

using ICSharpCode.ILSpyX.Settings;
Expand All @@ -32,7 +26,7 @@ namespace ICSharpCode.ILSpy.Options
/// Interaction logic for DecompilerSettingsPanel.xaml
/// </summary>
[ExportOptionPage(Title = nameof(Properties.Resources.Decompiler), Order = 10)]
internal partial class DecompilerSettingsPanel : UserControl, IOptionPage
internal partial class DecompilerSettingsPanel : IOptionPage
{
public DecompilerSettingsPanel()
{
Expand All @@ -59,58 +53,9 @@ public void Save(XElement root)
MainWindow.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols;
}

private void OnGroupChecked(object sender, RoutedEventArgs e)
{
CheckGroup((CollectionViewGroup)((CheckBox)sender).DataContext, true);
}
private void OnGroupUnchecked(object sender, RoutedEventArgs e)
{
CheckGroup((CollectionViewGroup)((CheckBox)sender).DataContext, false);
}

void CheckGroup(CollectionViewGroup group, bool value)
{
foreach (var item in group.Items)
{
switch (item)
{
case CollectionViewGroup subGroup:
CheckGroup(subGroup, value);
break;
case CSharpDecompilerSetting setting:
setting.IsEnabled = value;
break;
}
}
}

bool IsGroupChecked(CollectionViewGroup group)
{
bool value = true;
foreach (var item in group.Items)
{
switch (item)
{
case CollectionViewGroup subGroup:
value = value && IsGroupChecked(subGroup);
break;
case CSharpDecompilerSetting setting:
value = value && setting.IsEnabled;
break;
}
}
return value;
}

private void OnGroupLoaded(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
checkBox.IsChecked = IsGroupChecked((CollectionViewGroup)checkBox.DataContext);
}

public void LoadDefaults()
{
MainWindow.Instance.CurrentDecompilerSettings = new Decompiler.DecompilerSettings();
MainWindow.Instance.CurrentDecompilerSettings = new();
this.DataContext = new DecompilerSettingsViewModel(MainWindow.Instance.CurrentDecompilerSettings);
}
}
Expand Down
101 changes: 69 additions & 32 deletions ILSpy/Options/DecompilerSettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,83 +16,120 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes;

using TomsToolbox.Wpf;

namespace ICSharpCode.ILSpy.Options
{
public class DecompilerSettingsViewModel : INotifyPropertyChanged
public sealed class DecompilerSettingsViewModel : ObservableObjectBase
{
public CSharpDecompilerSetting[] Settings { get; set; }
public DecompilerSettingsGroupViewModel[] Settings { get; }

public DecompilerSettingsViewModel(Decompiler.DecompilerSettings settings)
{
Settings = typeof(Decompiler.DecompilerSettings).GetProperties()
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false)
.Select(p => new CSharpDecompilerSetting(p) { IsEnabled = (bool)p.GetValue(settings) })
.Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(settings) is true })
.OrderBy(item => item.Category, NaturalStringComparer.Instance)
.ThenBy(item => item.Description)
.GroupBy(p => p.Category)
.Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray()))
.ToArray();
}

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public Decompiler.DecompilerSettings ToDecompilerSettings()
{
var settings = new Decompiler.DecompilerSettings();
foreach (var item in Settings)

foreach (var item in Settings.SelectMany(group => group.Settings))
{
item.Property.SetValue(settings, item.IsEnabled);
}

return settings;
}
}
public class CSharpDecompilerSetting : INotifyPropertyChanged

public sealed class DecompilerSettingsGroupViewModel : ObservableObjectBase
{
bool isEnabled;
private bool? _areAllItemsChecked;

public CSharpDecompilerSetting(PropertyInfo p)
public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemViewModel[] settings)
{
this.Property = p;
this.Category = GetResourceString(p.GetCustomAttribute<CategoryAttribute>()?.Category ?? Resources.Other);
this.Description = GetResourceString(p.GetCustomAttribute<DescriptionAttribute>()?.Description ?? p.Name);
}
Settings = settings;
Category = category;

public PropertyInfo Property { get; }
_areAllItemsChecked = GetAreAllItemsChecked(Settings);

public bool IsEnabled {
get => isEnabled;
foreach (DecompilerSettingsItemViewModel viewModel in settings)
{
viewModel.PropertyChanged += Item_PropertyChanged;
}
}

public bool? AreAllItemsChecked {
get => _areAllItemsChecked;
set {
if (value != isEnabled)
SetProperty(ref _areAllItemsChecked, value);

if (!value.HasValue)
return;

foreach (var setting in Settings)
{
isEnabled = value;
OnPropertyChanged();
setting.IsEnabled = value.Value;
}
}
}

public string Description { get; set; }
public string Category { get; }

public string Category { get; set; }
public DecompilerSettingsItemViewModel[] Settings { get; }

public event PropertyChangedEventHandler PropertyChanged;
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(DecompilerSettingsItemViewModel.IsEnabled))
{
AreAllItemsChecked = GetAreAllItemsChecked(Settings);
}
}

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
private static bool? GetAreAllItemsChecked(ICollection<DecompilerSettingsItemViewModel> settings)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
var numberOfEnabledItems = settings.Count(item => item.IsEnabled);

if (numberOfEnabledItems == settings.Count)
return true;

if (numberOfEnabledItems == 0)
return false;

return null;
}
}

public sealed class DecompilerSettingsItemViewModel(PropertyInfo property) : ObservableObjectBase
{
private bool _isEnabled;

public PropertyInfo Property { get; } = property;

public bool IsEnabled {
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}

static string GetResourceString(string key)
public string Description { get; set; } = GetResourceString(property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? property.Name);

public string Category { get; set; } = GetResourceString(property.GetCustomAttribute<CategoryAttribute>()?.Category ?? Resources.Other);

private static string GetResourceString(string key)
{
var str = !string.IsNullOrEmpty(key) ? Resources.ResourceManager.GetString(key) : null;
return string.IsNullOrEmpty(key) || string.IsNullOrEmpty(str) ? key : str;
Expand Down
1 change: 0 additions & 1 deletion ILSpy/Options/DisplaySettingsPanel.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"
xmlns:toms="urn:TomsToolbox"
d:DataContext="{d:DesignInstance local:DisplaySettingsViewModel}">
<UserControl.Resources>
<local:FontSizeConverter x:Key="fontSizeConv" />
Expand Down

0 comments on commit fa0ab07

Please sign in to comment.