ImageResizerViewModel.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Collections.ObjectModel; 8 using System.Collections.Specialized; 9 using System.ComponentModel; 10 using System.IO; 11 using System.Linq; 12 using System.Runtime.CompilerServices; 13 using global::PowerToys.GPOWrapper; 14 using ManagedCommon; 15 using Microsoft.PowerToys.Settings.UI.Library; 16 using Microsoft.PowerToys.Settings.UI.Library.Helpers; 17 using Microsoft.PowerToys.Settings.UI.Library.Interfaces; 18 19 namespace Microsoft.PowerToys.Settings.UI.ViewModels; 20 21 public partial class ImageResizerViewModel : Observable 22 { 23 private static readonly string DefaultPresetNamePrefix = 24 Helpers.ResourceLoaderInstance.ResourceLoader.GetString("ImageResizer_DefaultSize_NewSizePrefix"); 25 26 private static readonly List<string> EncoderGuids = 27 [ 28 "1b7cfaf4-713f-473c-bbcd-6137425faeaf", // PNG Encoder 29 "0af1d87e-fcfe-4188-bdeb-a7906471cbe3", // Bitmap Encoder 30 "19e4a5aa-5662-4fc5-a0c0-1758028e1057", // JPEG Encoder 31 "163bcc30-e2e9-4f0b-961d-a3e9fdb788a3", // TIFF Encoder 32 "57a37caa-367a-4540-916b-f183c5093a4b", // TIFF Encoder 33 "1f8a5601-7d4d-4cbd-9c82-1bc8d4eeb9a5", // GIF Encoder 34 ]; 35 36 /// <summary> 37 /// Used to skip saving settings to file during initialization. 38 /// </summary> 39 private readonly bool _isInitializing; 40 41 /// <summary> 42 /// Holds defaults for new presets. 43 /// </summary> 44 private readonly ImageSize _customSize; 45 46 private GeneralSettings GeneralSettingsConfig { get; set; } 47 48 private readonly SettingsUtils _settingsUtils; 49 50 private ImageResizerSettings Settings { get; set; } 51 52 private const string ModuleName = ImageResizerSettings.ModuleName; 53 54 private Func<string, int> SendConfigMSG { get; } 55 56 public ImageResizerViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, Func<string, string> resourceLoader) 57 { 58 _isInitializing = true; 59 60 _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils)); 61 62 // To obtain the general settings configurations of PowerToys. 63 ArgumentNullException.ThrowIfNull(settingsRepository); 64 65 GeneralSettingsConfig = settingsRepository.SettingsConfig; 66 67 try 68 { 69 Settings = _settingsUtils.GetSettings<ImageResizerSettings>(ModuleName); 70 IdRecoveryHelper.RecoverInvalidIds(Settings.Properties.ImageresizerSizes.Value); 71 } 72 catch (Exception e) 73 { 74 Logger.LogError($"Exception encountered while reading {ModuleName} settings.", e); 75 #if DEBUG 76 if (e is ArgumentException || e is ArgumentNullException || e is PathTooLongException) 77 { 78 throw; 79 } 80 #endif 81 Settings = new ImageResizerSettings(resourceLoader); 82 _settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName); 83 } 84 85 // set the callback functions value to handle outgoing IPC message. 86 SendConfigMSG = ipcMSGCallBackFunc; 87 88 InitializeEnabledValue(); 89 90 Sizes = new ObservableCollection<ImageSize>(Settings.Properties.ImageresizerSizes.Value); 91 JPEGQualityLevel = Settings.Properties.ImageresizerJpegQualityLevel.Value; 92 PngInterlaceOption = Settings.Properties.ImageresizerPngInterlaceOption.Value; 93 TiffCompressOption = Settings.Properties.ImageresizerTiffCompressOption.Value; 94 FileName = Settings.Properties.ImageresizerFileName.Value; 95 KeepDateModified = Settings.Properties.ImageresizerKeepDateModified.Value; 96 Encoder = GetEncoderIndex(Settings.Properties.ImageresizerFallbackEncoder.Value); 97 98 _customSize = Settings.Properties.ImageresizerCustomSize.Value; 99 100 _isInitializing = false; 101 } 102 103 private void InitializeEnabledValue() 104 { 105 _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredImageResizerEnabledValue(); 106 if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) 107 { 108 // Get the enabled state from GPO. 109 _enabledStateIsGPOConfigured = true; 110 _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; 111 } 112 else 113 { 114 _isEnabled = GeneralSettingsConfig.Enabled.ImageResizer; 115 } 116 } 117 118 private GpoRuleConfigured _enabledGpoRuleConfiguration; 119 private bool _enabledStateIsGPOConfigured; 120 private bool _isEnabled; 121 private ObservableCollection<ImageSize> _sizes = []; 122 private int _jpegQualityLevel; 123 private int _pngInterlaceOption; 124 private int _tiffCompressOption; 125 private string _fileName; 126 private bool _keepDateModified; 127 private int _encoderGuidId; 128 129 public bool IsListViewFocusRequested { get; set; } 130 131 public bool IsEnabled 132 { 133 get => _isEnabled; 134 135 set 136 { 137 if (_enabledStateIsGPOConfigured) 138 { 139 // If it's GPO configured, shouldn't be able to change this state. 140 return; 141 } 142 143 if (value != _isEnabled) 144 { 145 // To set the status of ImageResizer in the General PowerToys settings. 146 _isEnabled = value; 147 GeneralSettingsConfig.Enabled.ImageResizer = value; 148 OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig); 149 150 SendConfigMSG(snd.ToString()); 151 OnPropertyChanged(nameof(IsEnabled)); 152 } 153 } 154 } 155 156 public bool IsEnabledGpoConfigured 157 { 158 get => _enabledStateIsGPOConfigured; 159 } 160 161 public ObservableCollection<ImageSize> Sizes 162 { 163 get => _sizes; 164 165 set 166 { 167 if (_sizes != null) 168 { 169 _sizes.CollectionChanged -= Sizes_CollectionChanged; 170 UnsubscribeFromItemPropertyChanged(_sizes); 171 } 172 173 _sizes = value; 174 175 if (_sizes != null) 176 { 177 _sizes.CollectionChanged += Sizes_CollectionChanged; 178 SubscribeToItemPropertyChanged(_sizes); 179 } 180 181 OnPropertyChanged(nameof(Sizes)); 182 183 if (!_isInitializing) 184 { 185 SaveImageSizes(); 186 } 187 } 188 } 189 190 private void Sizes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 191 { 192 SubscribeToItemPropertyChanged(e.NewItems?.Cast<ImageSize>()); 193 UnsubscribeFromItemPropertyChanged(e.OldItems?.Cast<ImageSize>()); 194 SaveImageSizes(); 195 } 196 197 private void SubscribeToItemPropertyChanged(IEnumerable<ImageSize> items) 198 { 199 if (items != null) 200 { 201 foreach (var item in items) 202 { 203 item.PropertyChanged += SizePropertyChanged; 204 } 205 } 206 } 207 208 private void UnsubscribeFromItemPropertyChanged(IEnumerable<ImageSize> items) 209 { 210 if (items != null) 211 { 212 foreach (var item in items) 213 { 214 item.PropertyChanged -= SizePropertyChanged; 215 } 216 } 217 } 218 219 private void SetProperty<T>(ref T backingField, T value, Action<T> updateSettingsAction, [CallerMemberName] string propertyName = null) 220 { 221 if (!EqualityComparer<T>.Default.Equals(backingField, value)) 222 { 223 backingField = value; 224 225 if (!_isInitializing) 226 { 227 updateSettingsAction(value); 228 _settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName); 229 } 230 231 OnPropertyChanged(propertyName); 232 } 233 } 234 235 public int JPEGQualityLevel 236 { 237 get => _jpegQualityLevel; 238 239 set 240 { 241 SetProperty(ref _jpegQualityLevel, value, v => Settings.Properties.ImageresizerJpegQualityLevel.Value = v); 242 } 243 } 244 245 public int PngInterlaceOption 246 { 247 get => _pngInterlaceOption; 248 249 set 250 { 251 SetProperty(ref _pngInterlaceOption, value, v => Settings.Properties.ImageresizerPngInterlaceOption.Value = v); 252 } 253 } 254 255 public int TiffCompressOption 256 { 257 get => _tiffCompressOption; 258 259 set 260 { 261 SetProperty(ref _tiffCompressOption, value, v => Settings.Properties.ImageresizerTiffCompressOption.Value = v); 262 } 263 } 264 265 public string FileName 266 { 267 get => _fileName; 268 269 set 270 { 271 if (!string.IsNullOrWhiteSpace(value)) 272 { 273 SetProperty(ref _fileName, value, v => Settings.Properties.ImageresizerFileName.Value = v); 274 } 275 } 276 } 277 278 public bool KeepDateModified 279 { 280 get => _keepDateModified; 281 282 set 283 { 284 SetProperty(ref _keepDateModified, value, v => Settings.Properties.ImageresizerKeepDateModified.Value = v); 285 } 286 } 287 288 public int Encoder 289 { 290 get => _encoderGuidId; 291 292 set 293 { 294 SetProperty(ref _encoderGuidId, value, v => Settings.Properties.ImageresizerFallbackEncoder.Value = GetEncoderGuid(v)); 295 } 296 } 297 298 public string EncoderGuid => GetEncoderGuid(_encoderGuidId); 299 300 public static string GetEncoderGuid(int index) => 301 index < 0 || index >= EncoderGuids.Count ? throw new ArgumentOutOfRangeException(nameof(index)) : EncoderGuids[index]; 302 303 public static int GetEncoderIndex(string encoderGuid) 304 { 305 int index = EncoderGuids.IndexOf(encoderGuid); 306 return index == -1 ? throw new ArgumentException("Encoder GUID not found.", nameof(encoderGuid)) : index; 307 } 308 309 public void AddImageSize(string namePrefix = "") 310 { 311 if (string.IsNullOrEmpty(namePrefix)) 312 { 313 namePrefix = DefaultPresetNamePrefix; 314 } 315 316 int maxId = Sizes.Count > 0 ? Sizes.Max(x => x.Id) : -1; 317 string sizeName = GenerateNameForNewSize(namePrefix); 318 319 Sizes.Add(new ImageSize(maxId + 1, GenerateNameForNewSize(namePrefix), _customSize.Fit, _customSize.Width, _customSize.Height, _customSize.Unit)); 320 321 // Set the focus requested flag to indicate that an add operation has occurred during the ContainerContentChanging event 322 IsListViewFocusRequested = true; 323 } 324 325 public void DeleteImageSize(int id) 326 { 327 ImageSize size = _sizes.First(x => x.Id == id); 328 Sizes.Remove(size); 329 } 330 331 public void SaveImageSizes() 332 { 333 Settings.Properties.ImageresizerSizes = new ImageResizerSizes(Sizes); 334 _settingsUtils.SaveSettings(Settings.Properties.ImageresizerSizes.ToJsonString(), ModuleName, "sizes.json"); 335 _settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName); 336 } 337 338 public void SizePropertyChanged(object sender, PropertyChangedEventArgs e) 339 { 340 SaveImageSizes(); 341 } 342 343 private string GenerateNameForNewSize(string namePrefix) 344 { 345 int newSizeCounter = 0; 346 347 foreach (var name in Sizes.Select(x => x.Name)) 348 { 349 if (name.StartsWith(namePrefix, StringComparison.InvariantCulture) && 350 int.TryParse(name.AsSpan(namePrefix.Length), out int number) && 351 newSizeCounter < number) 352 { 353 newSizeCounter = number; 354 } 355 } 356 357 return $"{namePrefix} {++newSizeCounter}"; 358 } 359 360 public void RefreshEnabledState() 361 { 362 InitializeEnabledValue(); 363 OnPropertyChanged(nameof(IsEnabled)); 364 } 365 }