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 }