[DSC] Add support for ImageresizerSizes property (#32657)

Add support for ImageresizerSizes property

- do not use ints for enums in ImageSize struct
- add required converters
- extend setAdditional functionality
- add samples

Co-authored-by: Andrey Nekrasov <1828123+yuyoyuppe@users.noreply.github.com>
This commit is contained in:
Andrey Nekrasov
2024-08-17 10:14:28 +02:00
committed by GitHub
parent 9af757f5ce
commit 1f5f43b154
8 changed files with 301 additions and 33 deletions

View File

@@ -24,4 +24,17 @@ properties:
FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F" FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
FileLocksmith: FileLocksmith:
Enabled: false Enabled: false
ImageResizer:
ImageResizerSizes:
- Name: Square2x
Width: 200
Height: 200
Unit: "Percent"
Fit: "Stretch"
- Name: MyInchSize
Width: 1024
Height: 1024
Unit: "Inch"
Fit: "Fit"
configurationVersion: 0.2.0 configurationVersion: 0.2.0

View File

@@ -21,7 +21,7 @@ internal sealed class DSCGeneration
public string Type; public string Type;
} }
private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } } }; private static readonly Dictionary<string, AdditionalPropertiesInfo> AdditionalPropertiesInfoPerModule = new Dictionary<string, AdditionalPropertiesInfo> { { "PowerLauncher", new AdditionalPropertiesInfo { Name = "Plugins", Type = "Hashtable[]" } }, { "ImageResizer", new AdditionalPropertiesInfo { Name = "ImageresizerSizes", Type = "Hashtable[]" } } };
private static string EmitEnumDefinition(Type type) private static string EmitEnumDefinition(Type type)
{ {

View File

@@ -16,38 +16,38 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{ {
Id = id; Id = id;
Name = string.Empty; Name = string.Empty;
Fit = (int)ResizeFit.Fit; Fit = ResizeFit.Fit;
Width = 0; Width = 0;
Height = 0; Height = 0;
Unit = (int)ResizeUnit.Pixel; Unit = ResizeUnit.Pixel;
} }
public ImageSize() public ImageSize()
{ {
Id = 0; Id = 0;
Name = string.Empty; Name = string.Empty;
Fit = (int)ResizeFit.Fit; Fit = ResizeFit.Fit;
Width = 0; Width = 0;
Height = 0; Height = 0;
Unit = (int)ResizeUnit.Pixel; Unit = ResizeUnit.Pixel;
} }
public ImageSize(int id, string name, ResizeFit fit, double width, double height, ResizeUnit unit) public ImageSize(int id, string name, ResizeFit fit, double width, double height, ResizeUnit unit)
{ {
Id = id; Id = id;
Name = name; Name = name;
Fit = (int)fit; Fit = fit;
Width = width; Width = width;
Height = height; Height = height;
Unit = (int)unit; Unit = unit;
} }
private int _id; private int _id;
private string _name; private string _name;
private int _fit; private ResizeFit _fit;
private double _height; private double _height;
private double _width; private double _width;
private int _unit; private ResizeUnit _unit;
public int Id public int Id
{ {
@@ -70,7 +70,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{ {
get get
{ {
if (Unit == 2 && Fit != 2) if (Unit == ResizeUnit.Percent && Fit != ResizeFit.Stretch)
{ {
return 0; return 0;
} }
@@ -85,7 +85,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{ {
get get
{ {
if (Unit == 2 && Fit != 2) if (Unit == ResizeUnit.Percent && Fit != ResizeFit.Stretch)
{ {
return false; return false;
} }
@@ -115,7 +115,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
} }
[JsonPropertyName("fit")] [JsonPropertyName("fit")]
public int Fit public ResizeFit Fit
{ {
get get
{ {
@@ -193,7 +193,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
} }
[JsonPropertyName("unit")] [JsonPropertyName("unit")]
public int Unit public ResizeUnit Unit
{ {
get get
{ {

View File

@@ -3,8 +3,11 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
@@ -28,18 +31,25 @@ public sealed class SetAdditionalSettingsCommandLineCommand
private struct AdditionalPropertyInfo private struct AdditionalPropertyInfo
{ {
public string RootPropertyName; // A path to the property starting from the root module Settings object in the following format: "RootPropertyA.NestedPropertyB[...]"
public JsonValueKind RootObjectType; public string PropertyPath;
// Property Type hint so we know how to handle it
public JsonValueKind PropertyType;
} }
private static readonly Dictionary<string, AdditionalPropertyInfo> SupportedAdditionalPropertiesInfoForModules = new Dictionary<string, AdditionalPropertyInfo> { { "PowerLauncher", new AdditionalPropertyInfo { RootPropertyName = "Plugins", RootObjectType = JsonValueKind.Array } } }; private static readonly Dictionary<string, AdditionalPropertyInfo> SupportedAdditionalPropertiesInfoForModules = new Dictionary<string, AdditionalPropertyInfo> { { "PowerLauncher", new AdditionalPropertyInfo { PropertyPath = "Plugins", PropertyType = JsonValueKind.Array } }, { "ImageResizer", new AdditionalPropertyInfo { PropertyPath = "Properties.ImageresizerSizes.Value", PropertyType = JsonValueKind.Array } } };
private static void ExecuteRootArray(JsonElement.ArrayEnumerator properties, IEnumerable<object> currentPropertyValuesArray) private static IEnumerable<object> ExecuteRootArray(IEnumerable<JsonElement> properties, IEnumerable<object> currentPropertyValuesArray)
{ {
// In case it's an array of object -> combine the existing values with the provided // In case it's an array of objects -> combine the existing values with the provided
var currentPropertyValueType = currentPropertyValuesArray.FirstOrDefault()?.GetType(); var result = currentPropertyValuesArray;
var currentPropertyValueType = GetUnderlyingTypeOfCollection(currentPropertyValuesArray);
object matchedElement = null; object matchedElement = null;
object newKeyPropertyValue = null;
foreach (var arrayElement in properties) foreach (var arrayElement in properties)
{ {
var newElementPropertyValues = new Dictionary<string, object>(); var newElementPropertyValues = new Dictionary<string, object>();
@@ -47,15 +57,16 @@ public sealed class SetAdditionalSettingsCommandLineCommand
{ {
var elementPropertyName = elementProperty.Name; var elementPropertyName = elementProperty.Name;
var elementPropertyType = currentPropertyValueType.GetProperty(elementPropertyName).PropertyType; var elementPropertyType = currentPropertyValueType.GetProperty(elementPropertyName).PropertyType;
var elemePropertyValue = ICmdLineRepresentable.ParseFor(elementPropertyType, elementProperty.Value.ToString()); var elementNewPropertyValue = ICmdLineRepresentable.ParseFor(elementPropertyType, elementProperty.Value.ToString());
if (elementPropertyName == KeyPropertyName) if (elementPropertyName == KeyPropertyName)
{ {
newKeyPropertyValue = elementNewPropertyValue;
foreach (var currentElementValue in currentPropertyValuesArray) foreach (var currentElementValue in currentPropertyValuesArray)
{ {
var currentElementType = currentElementValue.GetType(); var currentElementType = currentElementValue.GetType();
var keyPropertyNameInfo = currentElementType.GetProperty(KeyPropertyName); var keyPropertyNameInfo = currentElementType.GetProperty(KeyPropertyName);
var keyPropertyValue = keyPropertyNameInfo.GetValue(currentElementValue); var currentKeyPropertyValue = keyPropertyNameInfo.GetValue(currentElementValue);
if (string.Equals(keyPropertyValue, elemePropertyValue)) if (string.Equals(currentKeyPropertyValue, elementNewPropertyValue))
{ {
matchedElement = currentElementValue; matchedElement = currentElementValue;
break; break;
@@ -64,7 +75,18 @@ public sealed class SetAdditionalSettingsCommandLineCommand
} }
else else
{ {
newElementPropertyValues.Add(elementPropertyName, elemePropertyValue); newElementPropertyValues.Add(elementPropertyName, elementNewPropertyValue);
}
}
// Appending a new element -> create it first using a default ctor with 0 args and append it to the result
if (matchedElement == null)
{
newElementPropertyValues.Add(KeyPropertyName, newKeyPropertyValue);
matchedElement = Activator.CreateInstance(currentPropertyValueType);
if (matchedElement != null)
{
result = result.Append(matchedElement);
} }
} }
@@ -76,6 +98,148 @@ public sealed class SetAdditionalSettingsCommandLineCommand
propertyInfo.SetValue(matchedElement, overriddenProperty.Value); propertyInfo.SetValue(matchedElement, overriddenProperty.Value);
} }
} }
matchedElement = null;
}
return result;
}
private static object GetNestedPropertyValue(object obj, string propertyPath)
{
if (obj == null || string.IsNullOrWhiteSpace(propertyPath))
{
return null;
}
var properties = propertyPath.Split('.');
object currentObject = obj;
PropertyInfo currentProperty = null;
foreach (var property in properties)
{
if (currentObject == null)
{
return null;
}
currentProperty = currentObject.GetType().GetProperty(property);
if (currentProperty == null)
{
return null;
}
currentObject = currentProperty.GetValue(currentObject);
}
return currentObject;
}
// To apply changes to a generic collection, we must recreate it and assign it to the property
private static object CreateCompatibleCollection(Type collectionType, Type elementType, IEnumerable<object> newValues)
{
if (typeof(IList<>).MakeGenericType(elementType).IsAssignableFrom(collectionType) ||
typeof(ObservableCollection<>).MakeGenericType(elementType).IsAssignableFrom(collectionType))
{
var concreteType = typeof(List<>).MakeGenericType(elementType);
if (typeof(ObservableCollection<>).MakeGenericType(elementType).IsAssignableFrom(collectionType))
{
concreteType = typeof(ObservableCollection<>).MakeGenericType(elementType);
}
else if (collectionType.IsInterface || collectionType.IsAbstract)
{
concreteType = typeof(List<>).MakeGenericType(elementType);
}
var list = (IList)Activator.CreateInstance(concreteType);
foreach (var newValue in newValues)
{
list.Add(Convert.ChangeType(newValue, elementType, CultureInfo.InvariantCulture));
}
return list;
}
else if (typeof(IEnumerable<>).MakeGenericType(elementType).IsAssignableFrom(collectionType))
{
var listType = typeof(List<>).MakeGenericType(elementType);
var list = (IList)Activator.CreateInstance(listType);
foreach (var newValue in newValues)
{
list.Add(Convert.ChangeType(newValue, elementType, CultureInfo.InvariantCulture));
}
return list;
}
return null;
}
private static void SetNestedPropertyValue(object obj, string propertyPath, IEnumerable<object> newValues)
{
if (obj == null || string.IsNullOrWhiteSpace(propertyPath))
{
return;
}
var properties = propertyPath.Split('.');
object currentObject = obj;
PropertyInfo currentProperty = null;
for (int i = 0; i < properties.Length - 1; i++)
{
if (currentObject == null)
{
return;
}
currentProperty = currentObject.GetType().GetProperty(properties[i]);
if (currentProperty == null)
{
return;
}
currentObject = currentProperty.GetValue(currentObject);
}
if (currentObject == null)
{
return;
}
currentProperty = currentObject.GetType().GetProperty(properties.Last());
if (currentProperty == null)
{
return;
}
var propertyType = currentProperty.PropertyType;
var elementType = propertyType.GetGenericArguments()[0];
var newCollection = CreateCompatibleCollection(propertyType, elementType, newValues);
if (newCollection != null)
{
currentProperty.SetValue(currentObject, newCollection);
}
}
private static Type GetUnderlyingTypeOfCollection(IEnumerable<object> currentPropertyValuesArray)
{
Type collectionType = currentPropertyValuesArray.GetType();
if (!collectionType.IsGenericType)
{
throw new ArgumentException("Invalid json data supplied");
}
Type[] genericArguments = collectionType.GetGenericArguments();
if (genericArguments.Length > 0)
{
return genericArguments[0];
}
else
{
throw new ArgumentException("Invalid json data supplied");
} }
} }
@@ -91,14 +255,39 @@ public sealed class SetAdditionalSettingsCommandLineCommand
return; return;
} }
var propertyValueInfo = settingsConfigType.GetProperty(additionalPropertiesInfo.RootPropertyName); var currentPropertyValue = GetNestedPropertyValue(settingsConfig, additionalPropertiesInfo.PropertyPath);
var currentPropertyValue = propertyValueInfo.GetValue(settingsConfig);
// For now, only a certain data shapes are supported // For now, only a certain data shapes are supported
switch (additionalPropertiesInfo.RootObjectType) switch (additionalPropertiesInfo.PropertyType)
{ {
case JsonValueKind.Array: case JsonValueKind.Array:
ExecuteRootArray(settings.RootElement.EnumerateArray(), currentPropertyValue as IEnumerable<object>); if (currentPropertyValue == null)
{
currentPropertyValue = new JsonArray();
}
IEnumerable<JsonElement> propertiesToSet = null;
// Powershell ConvertTo-Json call omits wrapping a single value in an array, so we must do it here
if (settings.RootElement.ValueKind == JsonValueKind.Object)
{
var wrapperArray = new JsonArray();
wrapperArray.Add(settings.RootElement);
propertiesToSet = (IEnumerable<JsonElement>)wrapperArray.GetEnumerator();
}
else if (settings.RootElement.ValueKind == JsonValueKind.Array)
{
propertiesToSet = settings.RootElement.EnumerateArray().AsEnumerable();
}
else
{
throw new ArgumentException("Invalid json data supplied");
}
var newPropertyValue = ExecuteRootArray(propertiesToSet, currentPropertyValue as IEnumerable<object>);
SetNestedPropertyValue(settingsConfig, additionalPropertiesInfo.PropertyPath, newPropertyValue);
break; break;
default: default:
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -233,10 +233,10 @@ namespace ViewModelTests
// Assert // Assert
ImageSize newTestSize = viewModel.Sizes.First(x => x.Id == 0); ImageSize newTestSize = viewModel.Sizes.First(x => x.Id == 0);
Assert.AreEqual(newTestSize.Name, "New size 1"); Assert.AreEqual(newTestSize.Name, "New size 1");
Assert.AreEqual(newTestSize.Fit, (int)ResizeFit.Fit); Assert.AreEqual(newTestSize.Fit, ResizeFit.Fit);
Assert.AreEqual(newTestSize.Width, 854); Assert.AreEqual(newTestSize.Width, 854);
Assert.AreEqual(newTestSize.Height, 480); Assert.AreEqual(newTestSize.Height, 480);
Assert.AreEqual(newTestSize.Unit, (int)ResizeUnit.Pixel); Assert.AreEqual(newTestSize.Unit, ResizeUnit.Pixel);
} }
[TestMethod] [TestMethod]
@@ -287,10 +287,10 @@ namespace ViewModelTests
{ {
Id = 0, Id = 0,
Name = "Test", Name = "Test",
Fit = (int)ResizeFit.Fit, Fit = ResizeFit.Fit,
Width = 30, Width = 30,
Height = 30, Height = 30,
Unit = (int)ResizeUnit.Pixel, Unit = ResizeUnit.Pixel,
}; };
double negativeWidth = -2.0; double negativeWidth = -2.0;

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters;
public sealed class ImageResizerFitToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is ResizeFit)
{
return (int)value;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is int intValue)
{
return (ResizeFit)intValue;
}
return ResizeFit.Fill;
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters;
public sealed class ImageResizerUnitToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is ResizeUnit)
{
return (int)value;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is int intValue)
{
return (ResizeUnit)intValue;
}
return ResizeUnit.Centimeter;
}
}

View File

@@ -15,7 +15,9 @@
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <Page.Resources>
<converters:ImageResizerFitToStringConverter x:Key="ImageResizerFitToStringConverter" /> <converters:ImageResizerFitToStringConverter x:Key="ImageResizerFitToStringConverter" />
<converters:ImageResizerFitToIntConverter x:Key="ImageResizerFitToIntConverter" />
<converters:ImageResizerUnitToStringConverter x:Key="ImageResizerUnitToStringConverter" /> <converters:ImageResizerUnitToStringConverter x:Key="ImageResizerUnitToStringConverter" />
<converters:ImageResizerUnitToIntConverter x:Key="ImageResizerUnitToIntConverter" />
<toolkitconverters:BoolToObjectConverter <toolkitconverters:BoolToObjectConverter
x:Key="BoolToComboBoxIndexConverter" x:Key="BoolToComboBoxIndexConverter"
FalseValue="1" FalseValue="1"
@@ -120,7 +122,7 @@
x:Uid="ImageResizer_Fit" x:Uid="ImageResizer_Fit"
Width="240" Width="240"
HorizontalAlignment="Left" HorizontalAlignment="Left"
SelectedIndex="{x:Bind Fit, Mode=TwoWay}"> SelectedIndex="{x:Bind Fit, Mode=TwoWay, Converter={StaticResource ImageResizerFitToIntConverter}}">
<ComboBoxItem x:Uid="ImageResizer_Sizes_Fit_Fill" /> <ComboBoxItem x:Uid="ImageResizer_Sizes_Fit_Fill" />
<ComboBoxItem x:Uid="ImageResizer_Sizes_Fit_Fit" /> <ComboBoxItem x:Uid="ImageResizer_Sizes_Fit_Fit" />
<ComboBoxItem x:Uid="ImageResizer_Sizes_Fit_Stretch" /> <ComboBoxItem x:Uid="ImageResizer_Sizes_Fit_Stretch" />
@@ -146,7 +148,7 @@
<ComboBox <ComboBox
x:Uid="ImageResizer_Size" x:Uid="ImageResizer_Size"
Width="240" Width="240"
SelectedIndex="{Binding Unit, Mode=TwoWay}"> SelectedIndex="{Binding Unit, Mode=TwoWay, Converter={StaticResource ImageResizerUnitToIntConverter}}">
<ComboBoxItem x:Uid="ImageResizer_Sizes_Units_CM" /> <ComboBoxItem x:Uid="ImageResizer_Sizes_Units_CM" />
<ComboBoxItem x:Uid="ImageResizer_Sizes_Units_Inches" /> <ComboBoxItem x:Uid="ImageResizer_Sizes_Units_Inches" />
<ComboBoxItem x:Uid="ImageResizer_Sizes_Units_Percent" /> <ComboBoxItem x:Uid="ImageResizer_Sizes_Units_Percent" />