/ src / settings-ui / Settings.UI / ViewModels / ImageResizerViewModel.cs
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  }