/ src / settings-ui / Settings.UI / ViewModels / PowerOcrViewModel.cs
PowerOcrViewModel.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.Globalization;
  9  using System.Linq;
 10  using System.Text.Json;
 11  using System.Timers;
 12  using global::PowerToys.GPOWrapper;
 13  using ManagedCommon;
 14  using Microsoft.PowerToys.Settings.UI.Helpers;
 15  using Microsoft.PowerToys.Settings.UI.Library;
 16  using Microsoft.PowerToys.Settings.UI.Library.Helpers;
 17  using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
 18  using Microsoft.PowerToys.Settings.UI.SerializationContext;
 19  using Windows.Globalization;
 20  using Windows.Media.Ocr;
 21  
 22  namespace Microsoft.PowerToys.Settings.UI.ViewModels
 23  {
 24      public partial class PowerOcrViewModel : PageViewModelBase
 25      {
 26          protected override string ModuleName => PowerOcrSettings.ModuleName;
 27  
 28          private bool _disposed;
 29  
 30          // Delay saving of settings in order to avoid calling save multiple times and hitting file in use exception. If there is no other request to save settings in given interval, we proceed to save it; otherwise, we schedule saving it after this interval
 31          private const int SaveSettingsDelayInMs = 500;
 32  
 33          private GeneralSettings GeneralSettingsConfig { get; set; }
 34  
 35          private readonly SettingsUtils _settingsUtils;
 36          private readonly System.Threading.Lock _delayedActionLock = new System.Threading.Lock();
 37  
 38          private readonly PowerOcrSettings _powerOcrSettings;
 39          private Timer _delayedTimer;
 40  
 41          private GpoRuleConfigured _enabledGpoRuleConfiguration;
 42          private bool _enabledStateIsGPOConfigured;
 43          private bool _isEnabled;
 44          private int _languageIndex;
 45          private List<Language> possibleOcrLanguages;
 46  
 47          public ObservableCollection<string> AvailableLanguages { get; } = new ObservableCollection<string>();
 48  
 49          public int LanguageIndex
 50          {
 51              get
 52              {
 53                  return _languageIndex;
 54              }
 55  
 56              set
 57              {
 58                  if (value != _languageIndex)
 59                  {
 60                      _languageIndex = value;
 61                      if (_powerOcrSettings != null && _languageIndex < possibleOcrLanguages.Count && _languageIndex >= 0)
 62                      {
 63                          _powerOcrSettings.Properties.PreferredLanguage = possibleOcrLanguages[_languageIndex].NativeName;
 64                          NotifySettingsChanged();
 65                      }
 66  
 67                      OnPropertyChanged(nameof(LanguageIndex));
 68                  }
 69              }
 70          }
 71  
 72          private Func<string, int> SendConfigMSG { get; }
 73  
 74          public PowerOcrViewModel(
 75              SettingsUtils settingsUtils,
 76              ISettingsRepository<GeneralSettings> settingsRepository,
 77              ISettingsRepository<PowerOcrSettings> powerOcrsettingsRepository,
 78              Func<string, int> ipcMSGCallBackFunc)
 79          {
 80              // To obtain the general settings configurations of PowerToys Settings.
 81              ArgumentNullException.ThrowIfNull(settingsRepository);
 82  
 83              GeneralSettingsConfig = settingsRepository.SettingsConfig;
 84  
 85              // To obtain the settings configurations of Fancy zones.
 86              ArgumentNullException.ThrowIfNull(settingsRepository);
 87  
 88              _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
 89  
 90              ArgumentNullException.ThrowIfNull(powerOcrsettingsRepository);
 91  
 92              _powerOcrSettings = powerOcrsettingsRepository.SettingsConfig;
 93  
 94              InitializeEnabledValue();
 95  
 96              // set the callback functions value to handle outgoing IPC message.
 97              SendConfigMSG = ipcMSGCallBackFunc;
 98  
 99              _delayedTimer = new Timer();
100              _delayedTimer.Interval = SaveSettingsDelayInMs;
101              _delayedTimer.Elapsed += DelayedTimer_Tick;
102              _delayedTimer.AutoReset = false;
103          }
104  
105          private void InitializeEnabledValue()
106          {
107              _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredTextExtractorEnabledValue();
108              if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
109              {
110                  // Get the enabled state from GPO.
111                  _enabledStateIsGPOConfigured = true;
112                  _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
113              }
114              else
115              {
116                  _isEnabled = GeneralSettingsConfig.Enabled.PowerOcr;
117              }
118          }
119  
120          public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
121          {
122              var hotkeysDict = new Dictionary<string, HotkeySettings[]>
123              {
124                  [ModuleName] = [ActivationShortcut],
125              };
126  
127              return hotkeysDict;
128          }
129  
130          public bool IsEnabled
131          {
132              get => _isEnabled;
133              set
134              {
135                  if (_enabledStateIsGPOConfigured)
136                  {
137                      // If it's GPO configured, shouldn't be able to change this state.
138                      return;
139                  }
140  
141                  if (_isEnabled != value)
142                  {
143                      _isEnabled = value;
144                      OnPropertyChanged(nameof(IsEnabled));
145  
146                      // Set the status of PowerOcr in the general settings
147                      GeneralSettingsConfig.Enabled.PowerOcr = value;
148                      var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
149  
150                      SendConfigMSG(outgoing.ToString());
151                  }
152              }
153          }
154  
155          public bool IsWin11OrGreater
156          {
157              get => OSVersionHelper.IsWindows11();
158          }
159  
160          public bool IsEnabledGpoConfigured
161          {
162              get => _enabledStateIsGPOConfigured;
163          }
164  
165          public HotkeySettings ActivationShortcut
166          {
167              get => _powerOcrSettings.Properties.ActivationShortcut;
168              set
169              {
170                  if (_powerOcrSettings.Properties.ActivationShortcut != value)
171                  {
172                      _powerOcrSettings.Properties.ActivationShortcut = value ?? _powerOcrSettings.Properties.DefaultActivationShortcut;
173                      OnPropertyChanged(nameof(ActivationShortcut));
174  
175                      _settingsUtils.SaveSettings(_powerOcrSettings.ToJsonString(), PowerOcrSettings.ModuleName);
176                      NotifySettingsChanged();
177                  }
178              }
179          }
180  
181          internal void UpdateLanguages()
182          {
183              int preferredLanguageIndex = -1;
184              int systemLanguageIndex = -1;
185              CultureInfo systemCulture = CultureInfo.CurrentUICulture;
186  
187              // get the list of all installed OCR languages. While processing them, search for the previously preferred language and also for the current ui language
188              possibleOcrLanguages = OcrEngine.AvailableRecognizerLanguages.OrderBy(x => x.NativeName).ToList();
189              AvailableLanguages.Clear();
190              foreach (Language language in possibleOcrLanguages)
191              {
192                  if (_powerOcrSettings.Properties.PreferredLanguage?.Equals(language.DisplayName, StringComparison.Ordinal) == true)
193                  {
194                      preferredLanguageIndex = AvailableLanguages.Count;
195                  }
196  
197                  if (systemCulture.DisplayName.Equals(language.DisplayName, StringComparison.Ordinal) || systemCulture.Parent.DisplayName.Equals(language.DisplayName, StringComparison.Ordinal))
198                  {
199                      systemLanguageIndex = AvailableLanguages.Count;
200                  }
201  
202                  AvailableLanguages.Add(EnsureStartUpper(language.NativeName));
203              }
204  
205              // if the previously stored preferred language is not available (has been deleted or this is the first run with language preference)
206              if (preferredLanguageIndex == -1)
207              {
208                  // try to use the current ui language. If it is also not available, set the first language as preferred (to have any selected language)
209                  if (systemLanguageIndex >= 0)
210                  {
211                      preferredLanguageIndex = systemLanguageIndex;
212                  }
213                  else
214                  {
215                      preferredLanguageIndex = 0;
216                  }
217              }
218  
219              // set the language index -> the preferred language gets selected in the combo box
220              LanguageIndex = preferredLanguageIndex;
221          }
222  
223          private void ScheduleSavingOfSettings()
224          {
225              lock (_delayedActionLock)
226              {
227                  if (_delayedTimer.Enabled)
228                  {
229                      _delayedTimer.Stop();
230                  }
231  
232                  _delayedTimer.Start();
233              }
234          }
235  
236          private void DelayedTimer_Tick(object sender, EventArgs e)
237          {
238              lock (_delayedActionLock)
239              {
240                  _delayedTimer.Stop();
241                  NotifySettingsChanged();
242              }
243          }
244  
245          private void NotifySettingsChanged()
246          {
247              // Using InvariantCulture as this is an IPC message
248              SendConfigMSG(
249                     string.Format(
250                         CultureInfo.InvariantCulture,
251                         "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
252                         PowerOcrSettings.ModuleName,
253                         JsonSerializer.Serialize(_powerOcrSettings, SourceGenerationContextContext.Default.PowerOcrSettings)));
254          }
255  
256          public void RefreshEnabledState()
257          {
258              InitializeEnabledValue();
259              OnPropertyChanged(nameof(IsEnabled));
260          }
261  
262          protected override void Dispose(bool disposing)
263          {
264              if (!_disposed)
265              {
266                  if (disposing)
267                  {
268                      _delayedTimer?.Dispose();
269                      _delayedTimer = null;
270                  }
271  
272                  _disposed = true;
273              }
274  
275              base.Dispose(disposing);
276          }
277  
278          public string SnippingToolInfoBarMargin
279          {
280              // Workaround for wrong StackPanel behavior: On hidden controls the margin is still reserved.
281              get => IsWin11OrGreater ? "0,0,0,25" : "0,0,0,0";
282          }
283  
284          private string EnsureStartUpper(string input)
285          {
286              if (string.IsNullOrEmpty(input))
287              {
288                  return input;
289              }
290  
291              var inputArray = input.ToCharArray();
292              inputArray[0] = char.ToUpper(inputArray[0], CultureInfo.CurrentCulture);
293              return new string(inputArray);
294          }
295      }
296  }