mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
Migrate files from Wox to PowerLauncher (#5014)
* Moved all files from Wox to Powerlauncher * Removed Wox project * Changed namespace for imported files * Resolved errors for VM * Added build dependency order * Fixed errors in helper class * Remove Wox files * Fixed errors in SingleInstance class * Fixed wox.tests * Fixed MSI * Removed obsolete methods from PublicAPI * nit fixes * Throw null exception * Fix merge conflict
This commit is contained in:
committed by
GitHub
parent
177546bab6
commit
c85cd4ac24
@@ -0,0 +1,51 @@
|
||||
using Microsoft.PowerLauncher.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class ContextMenuItemViewModel : BaseModel
|
||||
{
|
||||
private ICommand _command;
|
||||
|
||||
public string PluginName { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Glyph { get; set; }
|
||||
public string FontFamily { get; set; }
|
||||
public ICommand Command {
|
||||
get
|
||||
{
|
||||
return this._command;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// ICommand does not implement the INotifyPropertyChanged interface and must call OnPropertyChanged() to prevent memory leaks
|
||||
if (value != this._command)
|
||||
{
|
||||
this._command = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Key AcceleratorKey { get; set; }
|
||||
public ModifierKeys AcceleratorModifiers { get; set; }
|
||||
public bool IsAcceleratorKeyEnabled { get; set; }
|
||||
|
||||
public void SendTelemetryEvent(LauncherResultActionEvent.TriggerType triggerType)
|
||||
{
|
||||
var eventData = new LauncherResultActionEvent()
|
||||
{
|
||||
PluginName = PluginName,
|
||||
Trigger = triggerType.ToString(),
|
||||
ActionName = Title
|
||||
|
||||
};
|
||||
PowerToysTelemetry.Log.WriteEvent(eventData);
|
||||
}
|
||||
}
|
||||
}
|
||||
802
src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs
Normal file
802
src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs
Normal file
@@ -0,0 +1,802 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Wox.Core.Plugin;
|
||||
using Wox.Core.Resource;
|
||||
using PowerLauncher.Helper;
|
||||
using Wox.Infrastructure;
|
||||
using Wox.Infrastructure.Hotkey;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
using Microsoft.PowerLauncher.Telemetry;
|
||||
using PowerLauncher.Storage;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using interop;
|
||||
using System.Globalization;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class MainViewModel : BaseModel, ISavable, IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private Query _lastQuery;
|
||||
private static Query _emptyQuery = new Query();
|
||||
private static bool _disposed;
|
||||
private string _queryTextBeforeLeaveResults;
|
||||
|
||||
private readonly WoxJsonStorage<QueryHistory> _historyItemsStorage;
|
||||
private readonly WoxJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
|
||||
private readonly WoxJsonStorage<TopMostRecord> _topMostRecordStorage;
|
||||
private readonly Settings _settings;
|
||||
private readonly QueryHistory _history;
|
||||
private readonly UserSelectedRecord _userSelectedRecord;
|
||||
private readonly TopMostRecord _topMostRecord;
|
||||
|
||||
private CancellationTokenSource _updateSource { get; set; }
|
||||
private CancellationToken _updateToken;
|
||||
private bool _saved;
|
||||
private HotkeyManager _hotkeyManager { get; set; }
|
||||
private ushort _hotkeyHandle;
|
||||
private readonly Internationalization _translator = InternationalizationManager.Instance;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public MainViewModel(Settings settings)
|
||||
{
|
||||
_hotkeyManager = new HotkeyManager();
|
||||
_saved = false;
|
||||
_queryTextBeforeLeaveResults = "";
|
||||
_lastQuery = _emptyQuery;
|
||||
_disposed = false;
|
||||
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
|
||||
_historyItemsStorage = new WoxJsonStorage<QueryHistory>();
|
||||
_userSelectedRecordStorage = new WoxJsonStorage<UserSelectedRecord>();
|
||||
_topMostRecordStorage = new WoxJsonStorage<TopMostRecord>();
|
||||
_history = _historyItemsStorage.Load();
|
||||
_userSelectedRecord = _userSelectedRecordStorage.Load();
|
||||
_topMostRecord = _topMostRecordStorage.Load();
|
||||
|
||||
ContextMenu = new ResultsViewModel(_settings);
|
||||
Results = new ResultsViewModel(_settings);
|
||||
History = new ResultsViewModel(_settings);
|
||||
_selectedResults = Results;
|
||||
|
||||
InitializeKeyCommands();
|
||||
RegisterResultsUpdatedEvent();
|
||||
|
||||
_settings.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Settings.Hotkey))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_settings.PreviousHotkey))
|
||||
{
|
||||
_hotkeyManager.UnregisterHotkey(_hotkeyHandle);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_settings.Hotkey))
|
||||
{
|
||||
SetHotkey(_settings.Hotkey, OnHotkey);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SetHotkey(_settings.Hotkey, OnHotkey);
|
||||
SetCustomPluginHotkey();
|
||||
}
|
||||
|
||||
private void RegisterResultsUpdatedEvent()
|
||||
{
|
||||
foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
|
||||
{
|
||||
var plugin = (IResultUpdated)pair.Plugin;
|
||||
plugin.ResultsUpdated += (s, e) =>
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
|
||||
UpdateResultView(e.Results, pair.Metadata, e.Query);
|
||||
}, _updateToken);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeKeyCommands()
|
||||
{
|
||||
IgnoreCommand = new RelayCommand(_ => {});
|
||||
|
||||
EscCommand = new RelayCommand(_ =>
|
||||
{
|
||||
if (!SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
}
|
||||
});
|
||||
|
||||
SelectNextItemCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectNextResult();
|
||||
});
|
||||
|
||||
SelectPrevItemCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectPrevResult();
|
||||
});
|
||||
|
||||
SelectNextTabItemCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectNextTabItem();
|
||||
});
|
||||
|
||||
SelectPrevTabItemCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectPrevTabItem();
|
||||
});
|
||||
|
||||
SelectNextPageCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectNextPage();
|
||||
});
|
||||
|
||||
SelectPrevPageCommand = new RelayCommand(_ =>
|
||||
{
|
||||
SelectedResults.SelectPrevPage();
|
||||
});
|
||||
|
||||
SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult());
|
||||
|
||||
StartHelpCommand = new RelayCommand(_ =>
|
||||
{
|
||||
Process.Start("http://doc.wox.one/");
|
||||
});
|
||||
|
||||
OpenResultCommand = new RelayCommand(index =>
|
||||
{
|
||||
var results = SelectedResults;
|
||||
|
||||
if (index != null)
|
||||
{
|
||||
results.SelectedIndex = int.Parse(index.ToString(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if(results.SelectedItem != null)
|
||||
{
|
||||
//If there is a context button selected fire the action for that button before the main command.
|
||||
bool didExecuteContextButton = results.SelectedItem.ExecuteSelectedContextButton();
|
||||
|
||||
if (!didExecuteContextButton)
|
||||
{
|
||||
var result = results.SelectedItem.Result;
|
||||
if (result != null && result.Action != null) // SelectedItem returns null if selection is empty.
|
||||
{
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
result.Action(new ActionContext
|
||||
{
|
||||
SpecialKeyState = KeyboardHelper.CheckModifiers()
|
||||
});
|
||||
});
|
||||
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
_userSelectedRecord.Add(result);
|
||||
_history.Add(result.OriginQuery.RawQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LoadContextMenuCommand = new RelayCommand(_ =>
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = ContextMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
});
|
||||
|
||||
LoadHistoryCommand = new RelayCommand(_ =>
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = History;
|
||||
History.SelectedIndex = _history.Items.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
});
|
||||
|
||||
ClearQueryCommand = new RelayCommand(_ =>
|
||||
{
|
||||
if(!string.IsNullOrEmpty(QueryText))
|
||||
{
|
||||
ChangeQueryText(string.Empty,true);
|
||||
//Push Event to UI SystemQuery has changed
|
||||
OnPropertyChanged(nameof(SystemQueryText));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ViewModel Properties
|
||||
|
||||
public Brush MainWindowBackground { get; set; }
|
||||
public Brush MainWindowBorderBrush { get; set; }
|
||||
|
||||
public ResultsViewModel Results { get; private set; }
|
||||
public ResultsViewModel ContextMenu { get; private set; }
|
||||
public ResultsViewModel History { get; private set; }
|
||||
|
||||
public string SystemQueryText { get; set; } = String.Empty;
|
||||
|
||||
public string QueryText { get; set; } = String.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// we need move cursor to end when we manually changed query
|
||||
/// but we don't want to move cursor to end when query is updated from TextBox.
|
||||
/// Also we don't want to force the results to change unless explicitly told to.
|
||||
/// </summary>
|
||||
/// <param name="queryText"></param>
|
||||
/// <param name="requery">Optional Parameter that if true, will automatically execute a query against the updated text</param>
|
||||
public void ChangeQueryText(string queryText, bool requery=false)
|
||||
{
|
||||
SystemQueryText = queryText;
|
||||
|
||||
if(requery)
|
||||
{
|
||||
QueryText = queryText;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
public bool LastQuerySelected { get; set; }
|
||||
|
||||
private ResultsViewModel _selectedResults;
|
||||
private ResultsViewModel SelectedResults
|
||||
{
|
||||
get { return _selectedResults; }
|
||||
set
|
||||
{
|
||||
_selectedResults = value;
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
ContextMenu.Visibility = Visibility.Collapsed;
|
||||
History.Visibility = Visibility.Collapsed;
|
||||
ChangeQueryText(_queryTextBeforeLeaveResults);
|
||||
}
|
||||
else
|
||||
{
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
_queryTextBeforeLeaveResults = QueryText;
|
||||
|
||||
|
||||
// Because of Fody's optimization
|
||||
// setter won't be called when property value is not changed.
|
||||
// so we need manually call Query()
|
||||
// http://stackoverflow.com/posts/25895769/revisions
|
||||
if (string.IsNullOrEmpty(QueryText))
|
||||
{
|
||||
Query();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueryText = string.Empty;
|
||||
}
|
||||
}
|
||||
_selectedResults.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility ProgressBarVisibility { get; set; }
|
||||
|
||||
private Visibility _visibility;
|
||||
|
||||
public Visibility MainWindowVisibility {
|
||||
get { return _visibility; }
|
||||
set {
|
||||
_visibility = value;
|
||||
if(value == Visibility.Visible)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new LauncherShowEvent());
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new LauncherHideEvent());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand IgnoreCommand { get; set; }
|
||||
public ICommand EscCommand { get; set; }
|
||||
public ICommand SelectNextItemCommand { get; set; }
|
||||
public ICommand SelectPrevItemCommand { get; set; }
|
||||
|
||||
public ICommand SelectNextTabItemCommand { get; set; }
|
||||
public ICommand SelectPrevTabItemCommand { get; set; }
|
||||
|
||||
public ICommand SelectNextPageCommand { get; set; }
|
||||
public ICommand SelectPrevPageCommand { get; set; }
|
||||
public ICommand SelectFirstResultCommand { get; set; }
|
||||
public ICommand StartHelpCommand { get; set; }
|
||||
public ICommand LoadContextMenuCommand { get; set; }
|
||||
public ICommand LoadHistoryCommand { get; set; }
|
||||
public ICommand OpenResultCommand { get; set; }
|
||||
public ICommand ClearQueryCommand { get; set; }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public void Query()
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
QueryResults();
|
||||
}
|
||||
else if (HistorySelected())
|
||||
{
|
||||
QueryHistory();
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryHistory()
|
||||
{
|
||||
const string id = "Query History ID";
|
||||
#pragma warning disable CA1308 // Normalize strings to uppercase
|
||||
var query = QueryText.ToLower(CultureInfo.InvariantCulture).Trim();
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
History.Clear();
|
||||
|
||||
var results = new List<Result>();
|
||||
foreach (var h in _history.Items)
|
||||
{
|
||||
var title = _translator.GetTranslation("executeQuery");
|
||||
var time = _translator.GetTranslation("lastExecuteTime");
|
||||
var result = new Result
|
||||
{
|
||||
Title = string.Format(CultureInfo.InvariantCulture, title, h.Query),
|
||||
SubTitle = string.Format(CultureInfo.InvariantCulture, time, h.ExecutedDateTime),
|
||||
IcoPath = "Images\\history.png",
|
||||
OriginQuery = new Query { RawQuery = h.Query },
|
||||
Action = _ =>
|
||||
{
|
||||
SelectedResults = Results;
|
||||
ChangeQueryText(h.Query);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
var filtered = results.Where
|
||||
(
|
||||
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() ||
|
||||
StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
|
||||
).ToList();
|
||||
History.AddResults(filtered, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
History.AddResults(results, id);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryResults()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(QueryText))
|
||||
{
|
||||
var queryTimer = new System.Diagnostics.Stopwatch();
|
||||
queryTimer.Start();
|
||||
_updateSource?.Cancel();
|
||||
var currentUpdateSource = new CancellationTokenSource();
|
||||
_updateSource = currentUpdateSource;
|
||||
var currentCancellationToken = _updateSource.Token;
|
||||
_updateToken = currentCancellationToken;
|
||||
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
if (query != null)
|
||||
{
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
|
||||
_lastQuery = query;
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
// so looping will stop once it was cancelled
|
||||
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(plugins, parallelOptions, plugin =>
|
||||
{
|
||||
if (!plugin.Metadata.Disabled)
|
||||
{
|
||||
var results = PluginManager.QueryForPlugin(plugin, query);
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
UpdateResultView(results, plugin.Metadata, query);
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
|
||||
// this should happen once after all queries are done so progress bar should continue
|
||||
// until the end of all querying
|
||||
if (currentUpdateSource == _updateSource)
|
||||
{ // update to hidden if this is still the current query
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
queryTimer.Stop();
|
||||
var queryEvent = new LauncherQueryEvent()
|
||||
{
|
||||
QueryTimeMs = queryTimer.ElapsedMilliseconds,
|
||||
NumResults = Results.Results.Count,
|
||||
QueryLength = query.RawQuery.Length
|
||||
};
|
||||
PowerToysTelemetry.Log.WriteEvent(queryEvent);
|
||||
|
||||
}, currentCancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
_lastQuery = _emptyQuery;
|
||||
Results.SelectedItem = null;
|
||||
Results.Clear();
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveOldQueryResults(Query query)
|
||||
{
|
||||
string lastKeyword = _lastQuery.ActionKeyword;
|
||||
string keyword = query.ActionKeyword;
|
||||
if (string.IsNullOrEmpty(lastKeyword))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyword))
|
||||
{
|
||||
Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata);
|
||||
}
|
||||
else if (lastKeyword != keyword)
|
||||
{
|
||||
Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool SelectedIsFromQueryResults()
|
||||
{
|
||||
var selected = SelectedResults == Results;
|
||||
return selected;
|
||||
}
|
||||
|
||||
private bool ContextMenuSelected()
|
||||
{
|
||||
var selected = SelectedResults == ContextMenu;
|
||||
return selected;
|
||||
}
|
||||
|
||||
|
||||
private bool HistorySelected()
|
||||
{
|
||||
var selected = SelectedResults == History;
|
||||
return selected;
|
||||
}
|
||||
#region Hotkey
|
||||
|
||||
private void SetHotkey(string hotkeyStr, HotkeyCallback action)
|
||||
{
|
||||
var hotkey = new HotkeyModel(hotkeyStr);
|
||||
SetHotkey(hotkey, action);
|
||||
}
|
||||
|
||||
private void SetHotkey(HotkeyModel hotkeyModel, HotkeyCallback action)
|
||||
{
|
||||
string hotkeyStr = hotkeyModel.ToString();
|
||||
try
|
||||
{
|
||||
Hotkey hotkey = new Hotkey();
|
||||
hotkey.Alt = hotkeyModel.Alt;
|
||||
hotkey.Shift = hotkeyModel.Shift;
|
||||
hotkey.Ctrl = hotkeyModel.Ctrl;
|
||||
hotkey.Win = hotkeyModel.Win;
|
||||
hotkey.Key = (byte) KeyInterop.VirtualKeyFromKey(hotkeyModel.CharKey);
|
||||
|
||||
_hotkeyHandle = _hotkeyManager.RegisterHotkey(hotkey, action);
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
string errorMsg = string.Format(CultureInfo.InvariantCulture, InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
|
||||
MessageBox.Show(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Wox should ignore any hotkeys
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool ShouldIgnoreHotkeys()
|
||||
{
|
||||
//double if to omit calling win32 function
|
||||
if (_settings.IgnoreHotkeysOnFullscreen)
|
||||
if (WindowsInteropHelper.IsWindowFullscreen())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetCustomPluginHotkey()
|
||||
{
|
||||
if (_settings.CustomPluginHotkeys == null) return;
|
||||
foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys)
|
||||
{
|
||||
SetHotkey(hotkey.Hotkey, () =>
|
||||
{
|
||||
if (ShouldIgnoreHotkeys()) return;
|
||||
MainWindowVisibility = Visibility.Visible;
|
||||
ChangeQueryText(hotkey.ActionKeyword);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHotkey()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (!ShouldIgnoreHotkeys())
|
||||
{
|
||||
|
||||
if (_settings.LastQueryMode == LastQueryMode.Empty)
|
||||
{
|
||||
ChangeQueryText(string.Empty);
|
||||
}
|
||||
else if (_settings.LastQueryMode == LastQueryMode.Preserved)
|
||||
{
|
||||
LastQuerySelected = true;
|
||||
}
|
||||
else if (_settings.LastQueryMode == LastQueryMode.Selected)
|
||||
{
|
||||
LastQuerySelected = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"wrong LastQueryMode: <{_settings.LastQueryMode}>");
|
||||
}
|
||||
|
||||
ToggleWox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ToggleWox()
|
||||
{
|
||||
if (MainWindowVisibility != Visibility.Visible)
|
||||
{
|
||||
MainWindowVisibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (!_saved)
|
||||
{
|
||||
_historyItemsStorage.Save();
|
||||
_userSelectedRecordStorage.Save();
|
||||
_topMostRecordStorage.Save();
|
||||
|
||||
_saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query originQuery)
|
||||
{
|
||||
if(list == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
if (originQuery == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(originQuery));
|
||||
}
|
||||
|
||||
foreach (var result in list)
|
||||
{
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) * 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (originQuery.RawQuery == _lastQuery.RawQuery)
|
||||
{
|
||||
Results.AddResults(list, metadata.ID);
|
||||
}
|
||||
|
||||
if (Results.Visibility != Visibility.Visible && list.Count > 0)
|
||||
{
|
||||
Results.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public void ColdStartFix()
|
||||
{
|
||||
// Fix Cold start for List view xaml island
|
||||
List<Result> list = new List<Result>();
|
||||
Result r = new Result
|
||||
{
|
||||
Title = "hello"
|
||||
};
|
||||
list.Add(r);
|
||||
Results.AddResults(list, "0");
|
||||
Results.Clear();
|
||||
MainWindowVisibility = System.Windows.Visibility.Collapsed;
|
||||
|
||||
// Fix Cold start for plugins
|
||||
string s = "m";
|
||||
var query = QueryBuilder.Build(s.Trim(), PluginManager.NonGlobalPlugins);
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
foreach (PluginPair plugin in plugins)
|
||||
{
|
||||
if (!plugin.Metadata.Disabled && plugin.Metadata.Name != "Window Walker")
|
||||
{
|
||||
var _ = PluginManager.QueryForPlugin(plugin, query);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void HandleContextMenu(Key AcceleratorKey, ModifierKeys AcceleratorModifiers)
|
||||
{
|
||||
var results = SelectedResults;
|
||||
if (results.SelectedItem != null)
|
||||
{
|
||||
foreach (ContextMenuItemViewModel contextMenuItems in results.SelectedItem.ContextMenuItems)
|
||||
{
|
||||
if (contextMenuItems.AcceleratorKey == AcceleratorKey && contextMenuItems.AcceleratorModifiers == AcceleratorModifiers)
|
||||
{
|
||||
MainWindowVisibility = Visibility.Collapsed;
|
||||
contextMenuItems.Command.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetAutoCompleteText(int index, string input, String query)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(query))
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
if (input.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
// Use the same case as the input query for the matched portion of the string
|
||||
return query + input.Substring(query.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetSearchText(int index, String input, string query)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
if (index == 0 && !string.IsNullOrEmpty(query))
|
||||
{
|
||||
if (input.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
return query + input.Substring(query.Length);
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_hotkeyHandle != 0)
|
||||
{
|
||||
_hotkeyManager.UnregisterHotkey(_hotkeyHandle);
|
||||
}
|
||||
_hotkeyManager.Dispose();
|
||||
_updateSource.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
31
src/modules/launcher/PowerLauncher/ViewModel/RelayCommand.cs
Normal file
31
src/modules/launcher/PowerLauncher/ViewModel/RelayCommand.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private Action<object> _action;
|
||||
|
||||
public RelayCommand(Action<object> action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public virtual bool CanExecute(object parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public virtual void Execute(object parameter)
|
||||
{
|
||||
_action?.Invoke(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
271
src/modules/launcher/PowerLauncher/ViewModel/ResultViewModel.cs
Normal file
271
src/modules/launcher/PowerLauncher/ViewModel/ResultViewModel.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Wox.Core.Plugin;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Plugin;
|
||||
using PowerLauncher.Helper;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class ResultViewModel : BaseModel
|
||||
{
|
||||
public enum ActivationType
|
||||
{
|
||||
Selection,
|
||||
Hover
|
||||
};
|
||||
|
||||
public ObservableCollection<ContextMenuItemViewModel> ContextMenuItems { get; } = new ObservableCollection<ContextMenuItemViewModel>();
|
||||
|
||||
public ICommand ActivateContextButtonsHoverCommand { get; set; }
|
||||
public ICommand ActivateContextButtonsSelectionCommand { get; set; }
|
||||
public ICommand DeactivateContextButtonsHoverCommand { get; set; }
|
||||
|
||||
public ICommand DeactivateContextButtonsSelectionCommand { get; set; }
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public bool IsHovered { get; set; }
|
||||
|
||||
public bool AreContextButtonsActive { get; set; }
|
||||
|
||||
public int ContextMenuSelectedIndex { get; set; }
|
||||
|
||||
const int NoSelectionIndex = -1;
|
||||
|
||||
public ResultViewModel(Result result)
|
||||
{
|
||||
if (result != null)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
ContextMenuSelectedIndex = NoSelectionIndex;
|
||||
LoadContextMenu();
|
||||
|
||||
ActivateContextButtonsHoverCommand = new RelayCommand(ActivateContextButtonsHoverAction);
|
||||
ActivateContextButtonsSelectionCommand = new RelayCommand(ActivateContextButtonsSelectionAction);
|
||||
DeactivateContextButtonsHoverCommand = new RelayCommand(DeactivateContextButtonsHoverAction);
|
||||
DeactivateContextButtonsSelectionCommand = new RelayCommand(DeactivateContextButtonsSelectionAction);
|
||||
}
|
||||
|
||||
private void ActivateContextButtonsHoverAction(object sender)
|
||||
{
|
||||
ActivateContextButtons(ActivationType.Hover);
|
||||
}
|
||||
|
||||
private void ActivateContextButtonsSelectionAction(object sender)
|
||||
{
|
||||
ActivateContextButtons(ActivationType.Selection);
|
||||
}
|
||||
public void ActivateContextButtons(ActivationType activationType)
|
||||
{
|
||||
// Result does not contain any context menu items - we don't need to show the context menu ListView at all.
|
||||
if (ContextMenuItems.Count > 0)
|
||||
{
|
||||
AreContextButtonsActive = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AreContextButtonsActive = false;
|
||||
}
|
||||
|
||||
if (activationType == ActivationType.Selection)
|
||||
{
|
||||
IsSelected = true;
|
||||
EnableContextMenuAcceleratorKeys();
|
||||
}
|
||||
else if (activationType == ActivationType.Hover)
|
||||
{
|
||||
IsHovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DeactivateContextButtonsHoverAction(object sender)
|
||||
{
|
||||
DeactivateContextButtons(ActivationType.Hover);
|
||||
}
|
||||
|
||||
private void DeactivateContextButtonsSelectionAction(object sender)
|
||||
{
|
||||
DeactivateContextButtons(ActivationType.Selection);
|
||||
}
|
||||
|
||||
public void DeactivateContextButtons(ActivationType activationType)
|
||||
{
|
||||
if (activationType == ActivationType.Selection)
|
||||
{
|
||||
IsSelected = false;
|
||||
DisableContextMenuAcceleratorkeys();
|
||||
}
|
||||
else if (activationType == ActivationType.Hover)
|
||||
{
|
||||
IsHovered = false;
|
||||
}
|
||||
|
||||
// Result does not contain any context menu items - we don't need to show the context menu ListView at all.
|
||||
if (ContextMenuItems?.Count > 0)
|
||||
{
|
||||
AreContextButtonsActive = IsSelected || IsHovered;
|
||||
}
|
||||
else
|
||||
{
|
||||
AreContextButtonsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void LoadContextMenu()
|
||||
{
|
||||
var results = PluginManager.GetContextMenusForPlugin(Result);
|
||||
ContextMenuItems.Clear();
|
||||
foreach (var r in results)
|
||||
{
|
||||
ContextMenuItems.Add(new ContextMenuItemViewModel()
|
||||
{
|
||||
PluginName = r.PluginName,
|
||||
Title = r.Title,
|
||||
Glyph = r.Glyph,
|
||||
FontFamily = r.FontFamily,
|
||||
AcceleratorKey = r.AcceleratorKey,
|
||||
AcceleratorModifiers = r.AcceleratorModifiers,
|
||||
Command = new RelayCommand(_ =>
|
||||
{
|
||||
bool hideWindow = r.Action != null && r.Action(new ActionContext
|
||||
{
|
||||
SpecialKeyState = KeyboardHelper.CheckModifiers()
|
||||
});
|
||||
|
||||
if (hideWindow)
|
||||
{
|
||||
//TODO - Do we hide the window
|
||||
// MainWindowVisibility = Visibility.Collapsed;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableContextMenuAcceleratorKeys()
|
||||
{
|
||||
foreach (var i in ContextMenuItems)
|
||||
{
|
||||
i.IsAcceleratorKeyEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisableContextMenuAcceleratorkeys()
|
||||
{
|
||||
foreach (var i in ContextMenuItems)
|
||||
{
|
||||
i.IsAcceleratorKeyEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource Image
|
||||
{
|
||||
get
|
||||
{
|
||||
var imagePath = Result.IcoPath;
|
||||
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Result.Icon();
|
||||
}
|
||||
#pragma warning disable CA1031 // Do not catch general exception types
|
||||
catch (Exception e)
|
||||
#pragma warning restore CA1031 // Do not catch general exception types
|
||||
{
|
||||
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
|
||||
imagePath = ImageLoader.ErrorIconPath;
|
||||
}
|
||||
}
|
||||
|
||||
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
|
||||
return ImageLoader.Load(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
//Returns false if we've already reached the last item.
|
||||
public bool SelectNextContextButton()
|
||||
{
|
||||
if (ContextMenuSelectedIndex == (ContextMenuItems.Count - 1))
|
||||
{
|
||||
ContextMenuSelectedIndex = NoSelectionIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
ContextMenuSelectedIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Returns false if we've already reached the first item.
|
||||
public bool SelectPrevContextButton()
|
||||
{
|
||||
if (ContextMenuSelectedIndex == NoSelectionIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ContextMenuSelectedIndex--;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SelectLastContextButton()
|
||||
{
|
||||
ContextMenuSelectedIndex = ContextMenuItems.Count - 1;
|
||||
}
|
||||
|
||||
public bool HasSelectedContextButton()
|
||||
{
|
||||
var isContextSelected = (ContextMenuSelectedIndex != NoSelectionIndex);
|
||||
return isContextSelected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the action on the selected context button
|
||||
/// </summary>
|
||||
/// <returns>False if there is nothing selected, otherwise true</returns>
|
||||
public bool ExecuteSelectedContextButton()
|
||||
{
|
||||
if (HasSelectedContextButton())
|
||||
{
|
||||
ContextMenuItems[ContextMenuSelectedIndex].Command.Execute(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Result Result { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var r = obj as ResultViewModel;
|
||||
if (r != null)
|
||||
{
|
||||
return Result.Equals(r.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Result.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var display = String.IsNullOrEmpty(Result.QueryTextDisplay) ? Result.Title : Result.QueryTextDisplay;
|
||||
return display;
|
||||
}
|
||||
}
|
||||
}
|
||||
294
src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs
Normal file
294
src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using PowerLauncher.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class ResultsViewModel : BaseModel
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
public ResultCollection Results { get; }
|
||||
|
||||
private readonly object _addResultsLock = new object();
|
||||
private readonly object _collectionLock = new object();
|
||||
private readonly Settings _settings;
|
||||
// private int MaxResults => _settings?.MaxResultsToShow ?? 6;
|
||||
|
||||
public ResultsViewModel()
|
||||
{
|
||||
Results = new ResultCollection();
|
||||
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
|
||||
}
|
||||
public ResultsViewModel(Settings settings) : this()
|
||||
{
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
_settings.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(_settings.MaxResultsToShow))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(MaxHeight));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public int MaxHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.MaxResultsToShow * 75;
|
||||
}
|
||||
}
|
||||
public int SelectedIndex { get; set; }
|
||||
|
||||
private ResultViewModel _selectedItem;
|
||||
public ResultViewModel SelectedItem
|
||||
{
|
||||
get { return _selectedItem; }
|
||||
set
|
||||
{
|
||||
//value can be null when selecting an item in a virtualized list
|
||||
if (value != null)
|
||||
{
|
||||
if (_selectedItem != null)
|
||||
{
|
||||
_selectedItem.DeactivateContextButtons(ResultViewModel.ActivationType.Selection);
|
||||
}
|
||||
|
||||
_selectedItem = value;
|
||||
_selectedItem.ActivateContextButtons(ResultViewModel.ActivationType.Selection);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItem = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Thickness Margin { get; set; }
|
||||
public Visibility Visibility { get; set; } = Visibility.Hidden;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
|
||||
{
|
||||
int index = 0;
|
||||
for (; index < list.Count; index++)
|
||||
{
|
||||
var result = list[index];
|
||||
if (newScore > result.Result.Score)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private int NewIndex(int i)
|
||||
{
|
||||
var n = Results.Count;
|
||||
if (n > 0)
|
||||
{
|
||||
i = (n + i) % n;
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SelectedIndex returns -1 if selection is empty.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SelectNextResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex + 1);
|
||||
}
|
||||
|
||||
public void SelectPrevResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex - 1);
|
||||
}
|
||||
|
||||
public void SelectNextPage()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex + _settings.MaxResultsToShow);
|
||||
}
|
||||
|
||||
public void SelectPrevPage()
|
||||
{
|
||||
SelectedIndex = NewIndex(SelectedIndex - _settings.MaxResultsToShow);
|
||||
}
|
||||
|
||||
public void SelectFirstResult()
|
||||
{
|
||||
SelectedIndex = NewIndex(0);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Results.Clear();
|
||||
}
|
||||
|
||||
public void RemoveResultsExcept(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
|
||||
}
|
||||
|
||||
public void RemoveResultsFor(PluginMetadata metadata)
|
||||
{
|
||||
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
|
||||
}
|
||||
|
||||
public void SelectNextTabItem()
|
||||
{
|
||||
//Do nothing if there is no selected item or we've selected the next context button
|
||||
if(!SelectedItem?.SelectNextContextButton() ?? true)
|
||||
{
|
||||
SelectNextResult();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPrevTabItem()
|
||||
{
|
||||
//Do nothing if there is no selected item or we've selected the previous context button
|
||||
if (!SelectedItem?.SelectPrevContextButton() ?? true)
|
||||
{
|
||||
//Tabbing backwards should highlight the last item of the previous row
|
||||
SelectPrevResult();
|
||||
SelectedItem.SelectLastContextButton();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void AddResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
lock (_addResultsLock)
|
||||
{
|
||||
var newResults = NewResults(newRawResults, resultId);
|
||||
|
||||
// update UI in one run, so it can avoid UI flickering
|
||||
Results.Update(newResults);
|
||||
|
||||
if (Results.Count > 0)
|
||||
{
|
||||
Margin = new Thickness { Top = 8 };
|
||||
SelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Margin = new Thickness { Top = 0 };
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
|
||||
{
|
||||
var results = Results.ToList();
|
||||
var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList();
|
||||
var oldResults = results.Where(r => r.Result.PluginID == resultId).ToList();
|
||||
|
||||
// Find the same results in A (old results) and B (new newResults)
|
||||
var sameResults = oldResults
|
||||
.Where(t1 => newResults.Any(x => x.Result.Equals(t1.Result)))
|
||||
.ToList();
|
||||
|
||||
// remove result of relative complement of B in A
|
||||
foreach (var result in oldResults.Except(sameResults))
|
||||
{
|
||||
results.Remove(result);
|
||||
}
|
||||
|
||||
// update result with B's score and index position
|
||||
foreach (var sameResult in sameResults)
|
||||
{
|
||||
int oldIndex = results.IndexOf(sameResult);
|
||||
int oldScore = results[oldIndex].Result.Score;
|
||||
var newResult = newResults[newResults.IndexOf(sameResult)];
|
||||
int newScore = newResult.Result.Score;
|
||||
if (newScore != oldScore)
|
||||
{
|
||||
var oldResult = results[oldIndex];
|
||||
|
||||
oldResult.Result.Score = newScore;
|
||||
oldResult.Result.OriginQuery = newResult.Result.OriginQuery;
|
||||
|
||||
results.RemoveAt(oldIndex);
|
||||
int newIndex = InsertIndexOf(newScore, results);
|
||||
results.Insert(newIndex, oldResult);
|
||||
}
|
||||
}
|
||||
|
||||
// insert result in relative complement of A in B
|
||||
foreach (var result in newResults.Except(sameResults))
|
||||
{
|
||||
int newIndex = InsertIndexOf(result.Result.Score, results);
|
||||
results.Insert(newIndex, result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FormattedText Dependency Property
|
||||
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
|
||||
"FormattedText",
|
||||
typeof(Inline),
|
||||
typeof(ResultsViewModel),
|
||||
new PropertyMetadata(null, FormattedTextPropertyChanged));
|
||||
|
||||
public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
|
||||
{
|
||||
if (textBlock != null)
|
||||
{
|
||||
textBlock.SetValue(FormattedTextProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Inline GetFormattedText(DependencyObject textBlock)
|
||||
{
|
||||
return (Inline)textBlock?.GetValue(FormattedTextProperty);
|
||||
}
|
||||
|
||||
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var textBlock = d as TextBlock;
|
||||
if (textBlock == null) return;
|
||||
|
||||
var inline = (Inline)e.NewValue;
|
||||
|
||||
textBlock.Inlines.Clear();
|
||||
if (inline == null) return;
|
||||
|
||||
textBlock.Inlines.Add(inline);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Globalization;
|
||||
using Wox.Core.Resource;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace PowerLauncher.ViewModel
|
||||
{
|
||||
public class SettingWindowViewModel : BaseModel
|
||||
{
|
||||
private readonly WoxJsonStorage<Settings> _storage;
|
||||
|
||||
public SettingWindowViewModel()
|
||||
{
|
||||
_storage = new WoxJsonStorage<Settings>();
|
||||
Settings = _storage.Load();
|
||||
Settings.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Settings.ActivateTimes))
|
||||
{
|
||||
OnPropertyChanged(nameof(ActivatedTimes));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Settings Settings { get; set; }
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_storage.Save();
|
||||
}
|
||||
|
||||
#region general
|
||||
|
||||
private static Internationalization _translater => InternationalizationManager.Instance;
|
||||
|
||||
#endregion
|
||||
|
||||
#region about
|
||||
|
||||
public string ActivatedTimes => string.Format(CultureInfo.InvariantCulture, _translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user