/ src / modules / launcher / PowerLauncher / ViewModel / ResultsViewModel.cs
ResultsViewModel.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.Linq;
  8  using System.Threading;
  9  using System.Windows;
 10  using System.Windows.Controls;
 11  using System.Windows.Data;
 12  using System.Windows.Documents;
 13  
 14  using PowerLauncher.Helper;
 15  using Wox.Infrastructure.UserSettings;
 16  using Wox.Plugin;
 17  
 18  namespace PowerLauncher.ViewModel
 19  {
 20      public class ResultsViewModel : BaseModel
 21      {
 22          private readonly object _collectionLock = new object();
 23  
 24          private readonly PowerToysRunSettings _settings;
 25          private readonly IMainViewModel _mainViewModel;
 26  
 27          public ResultsViewModel()
 28          {
 29              Results = new ResultCollection();
 30              BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
 31          }
 32  
 33          public ResultsViewModel(PowerToysRunSettings settings, IMainViewModel mainViewModel)
 34              : this()
 35          {
 36              _settings = settings ?? throw new ArgumentNullException(nameof(settings));
 37              _mainViewModel = mainViewModel;
 38              _settings.PropertyChanged += (s, e) =>
 39              {
 40                  if (e.PropertyName == nameof(_settings.MaxResultsToShow))
 41                  {
 42                      Application.Current.Dispatcher.Invoke(() =>
 43                      {
 44                          OnPropertyChanged(nameof(MaxHeight));
 45                      });
 46                  }
 47              };
 48          }
 49  
 50          public int MaxHeight
 51          {
 52              get
 53              {
 54                  return (_settings.MaxResultsToShow * 56) + 16;
 55              }
 56          }
 57  
 58          private int _selectedIndex;
 59  
 60          public int SelectedIndex
 61          {
 62              get => _selectedIndex;
 63              set
 64              {
 65                  if (_selectedIndex != value)
 66                  {
 67                      _selectedIndex = value;
 68                      OnPropertyChanged(nameof(SelectedIndex));
 69                  }
 70              }
 71          }
 72  
 73          private ResultViewModel _selectedItem;
 74  
 75          public ResultViewModel SelectedItem
 76          {
 77              get
 78              {
 79                  return _selectedItem;
 80              }
 81  
 82              set
 83              {
 84                  if (value != null)
 85                  {
 86                      if (_selectedItem != null)
 87                      {
 88                          _selectedItem.DeactivateContextButtons(ResultViewModel.ActivationType.Selection);
 89                      }
 90  
 91                      _selectedItem = value;
 92                      _selectedItem.ActivateContextButtons(ResultViewModel.ActivationType.Selection);
 93                  }
 94                  else
 95                  {
 96                      _selectedItem = value;
 97                  }
 98              }
 99          }
100  
101          private Visibility _visibility = Visibility.Collapsed;
102  
103          public Visibility Visibility
104          {
105              get => _visibility;
106              set
107              {
108                  if (_visibility != value)
109                  {
110                      _visibility = value;
111                      OnPropertyChanged(nameof(Visibility));
112                  }
113              }
114          }
115  
116          public ResultCollection Results { get; }
117  
118          private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
119          {
120              int index = 0;
121              for (; index < list.Count; index++)
122              {
123                  var result = list[index];
124                  if (newScore > result.Result.Score)
125                  {
126                      break;
127                  }
128              }
129  
130              return index;
131          }
132  
133          private int NewIndex(int i)
134          {
135              var n = Results.Count;
136              if (n > 0)
137              {
138                  i = (n + i) % n;
139                  return i;
140              }
141              else
142              {
143                  // SelectedIndex returns -1 if selection is empty.
144                  return -1;
145              }
146          }
147  
148          public void SelectNextResult()
149          {
150              SelectedIndex = NewIndex(SelectedIndex + 1);
151          }
152  
153          public void SelectPrevResult()
154          {
155              SelectedIndex = NewIndex(SelectedIndex - 1);
156          }
157  
158          public void SelectNextPage()
159          {
160              SelectedIndex = NewIndex(SelectedIndex + _settings.MaxResultsToShow);
161          }
162  
163          public void SelectPrevPage()
164          {
165              SelectedIndex = NewIndex(SelectedIndex - _settings.MaxResultsToShow);
166          }
167  
168          public void SelectFirstResult()
169          {
170              SelectedIndex = NewIndex(0);
171          }
172  
173          public void Clear()
174          {
175              Results.Clear();
176          }
177  
178          public void RemoveResultsExcept(PluginMetadata metadata)
179          {
180              Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
181          }
182  
183          public void RemoveResultsFor(PluginMetadata metadata)
184          {
185              Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
186          }
187  
188          public void SelectNextTabItem()
189          {
190              if (_settings.TabSelectsContextButtons)
191              {
192                  // Do nothing if there is no selected item or we've selected the next context button
193                  if (!SelectedItem?.SelectNextContextButton() ?? true)
194                  {
195                      SelectNextResult();
196                  }
197              }
198              else
199              {
200                  // Do nothing if there is no selected item
201                  if (SelectedItem != null)
202                  {
203                      SelectNextResult();
204                  }
205              }
206          }
207  
208          public void SelectPrevTabItem()
209          {
210              if (_settings.TabSelectsContextButtons)
211              {
212                  // Do nothing if there is no selected item or we've selected the previous context button
213                  if (!SelectedItem?.SelectPrevContextButton() ?? true)
214                  {
215                      // Tabbing backwards should highlight the last item of the previous row
216                      SelectPrevResult();
217                      SelectedItem?.SelectLastContextButton();
218                  }
219              }
220              else
221              {
222                  // Do nothing if there is no selected item
223                  if (SelectedItem != null)
224                  {
225                      // Tabbing backwards
226                      SelectPrevResult();
227                  }
228              }
229          }
230  
231          public void SelectNextContextMenuItem()
232          {
233              if (SelectedItem != null)
234              {
235                  if (!SelectedItem.SelectNextContextButton())
236                  {
237                      SelectedItem.SelectLastContextButton();
238                  }
239              }
240          }
241  
242          public void SelectPreviousContextMenuItem()
243          {
244              if (SelectedItem != null)
245              {
246                  SelectedItem.SelectPrevContextButton();
247              }
248          }
249  
250          public bool IsContextMenuItemSelected()
251          {
252              if (SelectedItem != null && SelectedItem.ContextMenuSelectedIndex != ResultViewModel.NoSelectionIndex)
253              {
254                  return true;
255              }
256              else
257              {
258                  return false;
259              }
260          }
261  
262          /// <summary>
263          /// Add new results to ResultCollection
264          /// </summary>
265          public void AddResults(List<Result> newRawResults, CancellationToken ct)
266          {
267              ArgumentNullException.ThrowIfNull(newRawResults);
268  
269              List<ResultViewModel> newResults = new List<ResultViewModel>(newRawResults.Count);
270              foreach (Result r in newRawResults)
271              {
272                  newResults.Add(new ResultViewModel(r, _mainViewModel, _settings));
273                  ct.ThrowIfCancellationRequested();
274              }
275  
276              Results.AddRange(newResults);
277          }
278  
279          public void Sort(MainViewModel.QueryTuningOptions options)
280          {
281              List<ResultViewModel> sorted = null;
282  
283              if (options.SearchQueryTuningEnabled)
284              {
285                  sorted = Results.OrderByDescending(x => x.Result.GetSortOrderScore(options.SearchClickedItemWeight)).ToList();
286              }
287              else
288              {
289                  sorted = Results.OrderByDescending(x => x.Result.GetSortOrderScore(5)).ToList();
290              }
291  
292              // remove history items in they are in the list as non-history items
293              foreach (var nonHistoryResult in sorted.Where(x => x.Result.Metadata.Name != "History").ToList())
294              {
295                  var historyToRemove = sorted.FirstOrDefault(x => x.Result.Metadata.Name == "History" && x.Result.Title == nonHistoryResult.Result.Title && x.Result.SubTitle == nonHistoryResult.Result.SubTitle);
296                  if (historyToRemove != null)
297                  {
298                      sorted.Remove(historyToRemove);
299                  }
300              }
301  
302              Clear();
303              Results.AddRange(sorted);
304          }
305  
306          public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
307              "FormattedText",
308              typeof(Inline),
309              typeof(ResultsViewModel),
310              new PropertyMetadata(null, FormattedTextPropertyChanged));
311  
312          public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
313          {
314              if (textBlock != null)
315              {
316                  textBlock.SetValue(FormattedTextProperty, value);
317              }
318          }
319  
320          public static Inline GetFormattedText(DependencyObject textBlock)
321          {
322              return (Inline)textBlock?.GetValue(FormattedTextProperty);
323          }
324  
325          private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
326          {
327              var textBlock = d as TextBlock;
328              if (textBlock == null)
329              {
330                  return;
331              }
332  
333              var inline = (Inline)e.NewValue;
334  
335              textBlock.Inlines.Clear();
336              if (inline == null)
337              {
338                  return;
339              }
340  
341              textBlock.Inlines.Add(inline);
342          }
343      }
344  }