2024-11-21 11:18:05 +00:00
|
|
|
|
using System;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
using System.Collections.Generic;
|
2016-06-22 23:22:41 +00:00
|
|
|
|
using System.Linq;
|
2016-03-28 00:09:40 +00:00
|
|
|
|
using System.Threading;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows;
|
2020-04-21 09:12:17 +00:00
|
|
|
|
using Flow.Launcher.Core.Plugin;
|
|
|
|
|
|
using Flow.Launcher.Core.Resource;
|
|
|
|
|
|
using Flow.Launcher.Infrastructure;
|
|
|
|
|
|
using Flow.Launcher.Infrastructure.Hotkey;
|
|
|
|
|
|
using Flow.Launcher.Infrastructure.Storage;
|
|
|
|
|
|
using Flow.Launcher.Infrastructure.UserSettings;
|
|
|
|
|
|
using Flow.Launcher.Plugin;
|
2020-04-23 10:10:20 +00:00
|
|
|
|
using Flow.Launcher.Plugin.SharedCommands;
|
2020-04-21 09:12:17 +00:00
|
|
|
|
using Flow.Launcher.Storage;
|
2020-11-16 00:32:11 +00:00
|
|
|
|
using Flow.Launcher.Infrastructure.Logger;
|
2021-06-11 04:40:07 +00:00
|
|
|
|
using Microsoft.VisualStudio.Threading;
|
2022-03-01 19:29:21 +00:00
|
|
|
|
using System.Text;
|
2021-02-15 09:58:15 +00:00
|
|
|
|
using System.Threading.Channels;
|
2021-06-21 11:56:20 +00:00
|
|
|
|
using ISavable = Flow.Launcher.Plugin.ISavable;
|
2022-09-19 20:32:17 +00:00
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
2022-11-20 06:14:43 +00:00
|
|
|
|
using System.Globalization;
|
2022-12-27 07:21:06 +00:00
|
|
|
|
using System.Windows.Input;
|
2023-02-25 07:00:19 +00:00
|
|
|
|
using System.ComponentModel;
|
2024-11-27 13:57:17 +00:00
|
|
|
|
using Flow.Launcher.Infrastructure.Image;
|
|
|
|
|
|
using System.Windows.Media;
|
2025-01-12 11:45:36 +00:00
|
|
|
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
2025-03-06 04:33:36 +00:00
|
|
|
|
using System.Windows.Threading;
|
2021-10-14 04:34:46 +00:00
|
|
|
|
|
2020-04-21 09:12:17 +00:00
|
|
|
|
namespace Flow.Launcher.ViewModel
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2022-09-19 20:32:17 +00:00
|
|
|
|
public partial class MainViewModel : BaseModel, ISavable
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
|
|
|
|
|
#region Private Fields
|
|
|
|
|
|
|
2019-12-13 22:17:05 +00:00
|
|
|
|
private bool _isQueryRunning;
|
2016-03-26 01:20:42 +00:00
|
|
|
|
private Query _lastQuery;
|
2016-06-23 21:17:47 +00:00
|
|
|
|
private string _queryTextBeforeLeaveResults;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2020-04-21 12:16:10 +00:00
|
|
|
|
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
|
|
|
|
|
|
private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
|
|
|
|
|
|
private readonly FlowLauncherJsonStorage<TopMostRecord> _topMostRecordStorage;
|
2016-06-23 21:17:47 +00:00
|
|
|
|
private readonly History _history;
|
2024-05-19 15:16:56 +00:00
|
|
|
|
private int lastHistoryIndex = 1;
|
2016-04-21 00:53:21 +00:00
|
|
|
|
private readonly UserSelectedRecord _userSelectedRecord;
|
|
|
|
|
|
private readonly TopMostRecord _topMostRecord;
|
2016-03-28 00:09:40 +00:00
|
|
|
|
|
2016-04-25 01:33:55 +00:00
|
|
|
|
private CancellationTokenSource _updateSource;
|
|
|
|
|
|
private CancellationToken _updateToken;
|
|
|
|
|
|
|
2019-12-13 22:17:05 +00:00
|
|
|
|
private readonly Internationalization _translator = InternationalizationManager.Instance;
|
2016-06-23 21:17:47 +00:00
|
|
|
|
|
2021-02-15 18:12:28 +00:00
|
|
|
|
private ChannelWriter<ResultsForUpdate> _resultsUpdateChannelWriter;
|
2020-12-04 13:53:34 +00:00
|
|
|
|
private Task _resultsViewUpdateTask;
|
2020-11-10 09:13:45 +00:00
|
|
|
|
|
2016-02-18 11:30:36 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Constructor
|
|
|
|
|
|
|
2025-01-12 11:45:36 +00:00
|
|
|
|
public MainViewModel()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2016-06-23 21:17:47 +00:00
|
|
|
|
_queryTextBeforeLeaveResults = "";
|
2016-03-26 01:20:42 +00:00
|
|
|
|
_queryText = "";
|
|
|
|
|
|
_lastQuery = new Query();
|
2016-04-21 00:53:21 +00:00
|
|
|
|
|
2025-01-12 11:45:36 +00:00
|
|
|
|
Settings = Ioc.Default.GetRequiredService<Settings>();
|
2022-10-12 04:01:50 +00:00
|
|
|
|
Settings.PropertyChanged += (_, args) =>
|
2021-10-22 04:31:51 +00:00
|
|
|
|
{
|
2022-12-11 17:00:48 +00:00
|
|
|
|
switch (args.PropertyName)
|
2021-10-22 04:31:51 +00:00
|
|
|
|
{
|
2022-11-24 07:29:05 +00:00
|
|
|
|
case nameof(Settings.WindowSize):
|
|
|
|
|
|
OnPropertyChanged(nameof(MainWindowWidth));
|
|
|
|
|
|
break;
|
2024-05-14 07:54:15 +00:00
|
|
|
|
case nameof(Settings.WindowHeightSize):
|
|
|
|
|
|
OnPropertyChanged(nameof(MainWindowHeight));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.QueryBoxFontSize):
|
|
|
|
|
|
OnPropertyChanged(nameof(QueryBoxFontSize));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.ItemHeightSize):
|
|
|
|
|
|
OnPropertyChanged(nameof(ItemHeightSize));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.ResultItemFontSize):
|
|
|
|
|
|
OnPropertyChanged(nameof(ResultItemFontSize));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.ResultSubItemFontSize):
|
|
|
|
|
|
OnPropertyChanged(nameof(ResultSubItemFontSize));
|
|
|
|
|
|
break;
|
2022-12-20 14:35:07 +00:00
|
|
|
|
case nameof(Settings.AlwaysStartEn):
|
|
|
|
|
|
OnPropertyChanged(nameof(StartWithEnglishMode));
|
|
|
|
|
|
break;
|
2022-12-22 17:08:06 +00:00
|
|
|
|
case nameof(Settings.OpenResultModifiers):
|
|
|
|
|
|
OnPropertyChanged(nameof(OpenResultCommandModifiers));
|
|
|
|
|
|
break;
|
2022-12-22 16:58:26 +00:00
|
|
|
|
case nameof(Settings.PreviewHotkey):
|
2022-12-27 17:09:32 +00:00
|
|
|
|
OnPropertyChanged(nameof(PreviewHotkey));
|
2022-12-22 16:58:26 +00:00
|
|
|
|
break;
|
2024-04-13 09:47:37 +00:00
|
|
|
|
case nameof(Settings.AutoCompleteHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(AutoCompleteHotkey));
|
|
|
|
|
|
break;
|
2024-05-15 16:11:27 +00:00
|
|
|
|
case nameof(Settings.CycleHistoryUpHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(CycleHistoryUpHotkey));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.CycleHistoryDownHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(CycleHistoryDownHotkey));
|
|
|
|
|
|
break;
|
2024-04-18 12:01:01 +00:00
|
|
|
|
case nameof(Settings.AutoCompleteHotkey2):
|
|
|
|
|
|
OnPropertyChanged(nameof(AutoCompleteHotkey2));
|
|
|
|
|
|
break;
|
2024-04-15 12:52:15 +00:00
|
|
|
|
case nameof(Settings.SelectNextItemHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectNextItemHotkey));
|
|
|
|
|
|
break;
|
2024-04-18 12:01:01 +00:00
|
|
|
|
case nameof(Settings.SelectNextItemHotkey2):
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectNextItemHotkey2));
|
|
|
|
|
|
break;
|
2024-04-15 12:52:15 +00:00
|
|
|
|
case nameof(Settings.SelectPrevItemHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectPrevItemHotkey));
|
|
|
|
|
|
break;
|
2024-04-18 12:01:01 +00:00
|
|
|
|
case nameof(Settings.SelectPrevItemHotkey2):
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectPrevItemHotkey2));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.SelectNextPageHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectNextPageHotkey));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case nameof(Settings.SelectPrevPageHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectPrevPageHotkey));
|
|
|
|
|
|
break;
|
2024-04-18 07:35:05 +00:00
|
|
|
|
case nameof(Settings.OpenContextMenuHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(OpenContextMenuHotkey));
|
|
|
|
|
|
break;
|
2024-04-18 07:01:26 +00:00
|
|
|
|
case nameof(Settings.SettingWindowHotkey):
|
|
|
|
|
|
OnPropertyChanged(nameof(SettingWindowHotkey));
|
|
|
|
|
|
break;
|
2021-10-22 04:31:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2016-04-21 00:53:21 +00:00
|
|
|
|
|
2020-04-21 12:16:10 +00:00
|
|
|
|
_historyItemsStorage = new FlowLauncherJsonStorage<History>();
|
|
|
|
|
|
_userSelectedRecordStorage = new FlowLauncherJsonStorage<UserSelectedRecord>();
|
|
|
|
|
|
_topMostRecordStorage = new FlowLauncherJsonStorage<TopMostRecord>();
|
2016-06-23 21:17:47 +00:00
|
|
|
|
_history = _historyItemsStorage.Load();
|
2016-04-21 00:53:21 +00:00
|
|
|
|
_userSelectedRecord = _userSelectedRecordStorage.Load();
|
|
|
|
|
|
_topMostRecord = _topMostRecordStorage.Load();
|
2016-03-26 01:20:42 +00:00
|
|
|
|
|
2022-11-19 05:15:02 +00:00
|
|
|
|
ContextMenu = new ResultsViewModel(Settings)
|
|
|
|
|
|
{
|
2024-05-30 14:49:36 +00:00
|
|
|
|
LeftClickResultCommand = OpenResultCommand,
|
|
|
|
|
|
RightClickResultCommand = LoadContextMenuCommand,
|
|
|
|
|
|
IsPreviewOn = Settings.AlwaysPreview
|
2022-11-19 05:15:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
Results = new ResultsViewModel(Settings)
|
|
|
|
|
|
{
|
2024-05-30 14:49:36 +00:00
|
|
|
|
LeftClickResultCommand = OpenResultCommand,
|
|
|
|
|
|
RightClickResultCommand = LoadContextMenuCommand,
|
|
|
|
|
|
IsPreviewOn = Settings.AlwaysPreview
|
2022-11-19 05:15:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
History = new ResultsViewModel(Settings)
|
|
|
|
|
|
{
|
2024-05-30 14:49:36 +00:00
|
|
|
|
LeftClickResultCommand = OpenResultCommand,
|
|
|
|
|
|
RightClickResultCommand = LoadContextMenuCommand,
|
|
|
|
|
|
IsPreviewOn = Settings.AlwaysPreview
|
2022-11-19 05:15:02 +00:00
|
|
|
|
};
|
2016-06-22 23:26:57 +00:00
|
|
|
|
_selectedResults = Results;
|
|
|
|
|
|
|
2022-12-22 16:32:29 +00:00
|
|
|
|
Results.PropertyChanged += (_, args) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (args.PropertyName)
|
|
|
|
|
|
{
|
|
|
|
|
|
case nameof(Results.SelectedItem):
|
|
|
|
|
|
UpdatePreview();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2022-09-19 20:32:17 +00:00
|
|
|
|
|
2021-01-09 04:17:02 +00:00
|
|
|
|
RegisterViewUpdate();
|
2021-01-26 08:29:06 +00:00
|
|
|
|
RegisterResultsUpdatedEvent();
|
2022-12-23 03:47:06 +00:00
|
|
|
|
_ = RegisterClockAndDateUpdateAsync();
|
2016-03-28 02:09:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-04 13:53:34 +00:00
|
|
|
|
private void RegisterViewUpdate()
|
2020-11-10 09:13:45 +00:00
|
|
|
|
{
|
2021-02-15 18:12:28 +00:00
|
|
|
|
var resultUpdateChannel = Channel.CreateUnbounded<ResultsForUpdate>();
|
|
|
|
|
|
_resultsUpdateChannelWriter = resultUpdateChannel.Writer;
|
2021-01-09 04:17:02 +00:00
|
|
|
|
_resultsViewUpdateTask =
|
|
|
|
|
|
Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
|
2020-11-10 09:13:45 +00:00
|
|
|
|
|
2020-12-04 13:53:34 +00:00
|
|
|
|
async Task updateAction()
|
2020-11-10 09:13:45 +00:00
|
|
|
|
{
|
2021-01-14 11:54:37 +00:00
|
|
|
|
var queue = new Dictionary<string, ResultsForUpdate>();
|
2021-02-15 18:12:28 +00:00
|
|
|
|
var channelReader = resultUpdateChannel.Reader;
|
2021-02-15 09:58:15 +00:00
|
|
|
|
|
|
|
|
|
|
// it is not supposed to be false because it won't be complete
|
2021-02-15 18:12:28 +00:00
|
|
|
|
while (await channelReader.WaitToReadAsync())
|
2020-11-10 09:13:45 +00:00
|
|
|
|
{
|
2020-11-21 04:01:57 +00:00
|
|
|
|
await Task.Delay(20);
|
2021-02-15 18:12:28 +00:00
|
|
|
|
while (channelReader.TryRead(out var item))
|
2021-01-14 11:54:37 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (!item.Token.IsCancellationRequested)
|
|
|
|
|
|
queue[item.ID] = item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UpdateResultView(queue.Values);
|
2021-02-15 10:19:30 +00:00
|
|
|
|
queue.Clear();
|
2020-11-10 09:13:45 +00:00
|
|
|
|
}
|
2021-02-15 09:58:15 +00:00
|
|
|
|
|
|
|
|
|
|
Log.Error("MainViewModel", "Unexpected ResultViewUpdate ends");
|
2021-10-20 03:10:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-04 13:53:34 +00:00
|
|
|
|
void continueAction(Task t)
|
|
|
|
|
|
{
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
throw t.Exception;
|
|
|
|
|
|
#else
|
|
|
|
|
|
Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}");
|
2021-06-11 04:40:07 +00:00
|
|
|
|
_resultsViewUpdateTask =
|
2021-02-12 03:45:31 +00:00
|
|
|
|
Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
|
2020-12-04 13:53:34 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
}
|
2020-11-10 09:13:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
private void RegisterResultsUpdatedEvent()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2016-05-05 15:08:44 +00:00
|
|
|
|
foreach (var pair in PluginManager.GetPluginsForInterface<IResultUpdated>())
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2021-01-12 23:35:48 +00:00
|
|
|
|
var plugin = (IResultUpdated)pair.Plugin;
|
2016-05-05 15:08:44 +00:00
|
|
|
|
plugin.ResultsUpdated += (s, e) =>
|
2016-03-26 01:20:42 +00:00
|
|
|
|
{
|
2021-07-07 17:38:57 +00:00
|
|
|
|
if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested)
|
2016-05-05 20:15:13 +00:00
|
|
|
|
{
|
2021-07-07 17:38:57 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var token = e.Token == default ? _updateToken : e.Token;
|
|
|
|
|
|
|
2025-02-20 10:06:15 +00:00
|
|
|
|
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model
|
|
|
|
|
|
var resultsCopy = DeepCloneResults(e.Results, token);
|
2024-12-08 04:02:04 +00:00
|
|
|
|
|
|
|
|
|
|
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
|
|
|
|
|
|
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
|
2024-03-20 05:47:14 +00:00
|
|
|
|
token)))
|
2021-07-07 17:38:57 +00:00
|
|
|
|
{
|
|
|
|
|
|
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
2021-01-12 23:35:48 +00:00
|
|
|
|
}
|
2016-05-05 15:08:44 +00:00
|
|
|
|
};
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-11-27 02:38:47 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task ReloadPluginDataAsync()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
Hide();
|
2021-10-14 00:00:58 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
await PluginManager.ReloadDataAsync().ConfigureAwait(false);
|
2024-03-20 05:47:14 +00:00
|
|
|
|
Notification.Show(InternationalizationManager.Instance.GetTranslation("success"),
|
|
|
|
|
|
InternationalizationManager.Instance.GetTranslation("completedSuccessfully"));
|
2022-11-19 18:08:32 +00:00
|
|
|
|
}
|
2021-10-14 00:00:58 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void LoadHistory()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
if (SelectedIsFromQueryResults())
|
2016-02-12 09:20:46 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
SelectedResults = History;
|
|
|
|
|
|
History.SelectedIndex = _history.Items.Count - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2021-10-14 00:00:58 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
SelectedResults = Results;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-12 06:21:12 +00:00
|
|
|
|
|
2023-06-06 10:06:39 +00:00
|
|
|
|
[RelayCommand]
|
2024-02-23 21:25:29 +00:00
|
|
|
|
public void ReQuery()
|
2023-06-06 10:06:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (SelectedIsFromQueryResults())
|
|
|
|
|
|
{
|
2023-06-11 13:19:41 +00:00
|
|
|
|
QueryResults(isReQuery: true);
|
2023-06-06 10:06:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-25 02:05:54 +00:00
|
|
|
|
public void ReQuery(bool reselect)
|
|
|
|
|
|
{
|
2025-02-22 11:32:08 +00:00
|
|
|
|
BackToQueryResults();
|
|
|
|
|
|
QueryResults(isReQuery: true, reSelect: reselect);
|
2024-02-25 02:05:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-15 16:11:27 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
public void ReverseHistory()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_history.Items.Count > 0)
|
|
|
|
|
|
{
|
2024-05-19 15:16:56 +00:00
|
|
|
|
ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString());
|
|
|
|
|
|
if (lastHistoryIndex < _history.Items.Count)
|
2024-05-15 16:11:27 +00:00
|
|
|
|
{
|
2024-05-19 15:16:56 +00:00
|
|
|
|
lastHistoryIndex++;
|
2024-05-15 16:11:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
public void ForwardHistory()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_history.Items.Count > 0)
|
|
|
|
|
|
{
|
2024-05-19 15:16:56 +00:00
|
|
|
|
ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString());
|
|
|
|
|
|
if (lastHistoryIndex > 1)
|
2024-05-15 16:11:27 +00:00
|
|
|
|
{
|
2024-05-19 15:16:56 +00:00
|
|
|
|
lastHistoryIndex--;
|
2024-05-15 16:11:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void LoadContextMenu()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SelectedIsFromQueryResults())
|
|
|
|
|
|
{
|
|
|
|
|
|
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
|
|
|
|
|
|
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
|
|
|
|
|
|
if (SelectedResults.SelectedItem != null)
|
|
|
|
|
|
SelectedResults = ContextMenu;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults = Results;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-12 06:21:12 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void Backspace(object index)
|
|
|
|
|
|
{
|
|
|
|
|
|
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
2016-02-12 06:21:12 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
// GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
|
|
|
|
|
|
var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\'));
|
2016-02-12 06:21:12 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
var actionKeyword = string.IsNullOrEmpty(query.ActionKeyword) ? string.Empty : $"{query.ActionKeyword} ";
|
2016-02-12 06:21:12 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
ChangeQueryText($"{actionKeyword}{path}");
|
|
|
|
|
|
}
|
2019-12-07 14:57:21 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void AutocompleteQuery()
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = SelectedResults.SelectedItem?.Result;
|
|
|
|
|
|
if (result != null && SelectedIsFromQueryResults()) // SelectedItem returns null if selection is empty.
|
2016-02-12 09:20:46 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
var autoCompleteText = result.Title;
|
2016-03-26 01:20:42 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(result.AutoCompleteText))
|
2016-03-26 01:20:42 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
autoCompleteText = result.AutoCompleteText;
|
2016-03-26 01:20:42 +00:00
|
|
|
|
}
|
2022-11-19 18:08:32 +00:00
|
|
|
|
else if (!string.IsNullOrEmpty(SelectedResults.SelectedItem?.QuerySuggestionText))
|
2016-02-12 06:21:12 +00:00
|
|
|
|
{
|
2024-02-04 15:12:21 +00:00
|
|
|
|
var defaultSuggestion = SelectedResults.SelectedItem.QuerySuggestionText;
|
|
|
|
|
|
// check if result.actionkeywordassigned is empty
|
|
|
|
|
|
if (!string.IsNullOrEmpty(result.ActionKeywordAssigned))
|
|
|
|
|
|
{
|
|
|
|
|
|
autoCompleteText = $"{result.ActionKeywordAssigned} {defaultSuggestion}";
|
|
|
|
|
|
}
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
autoCompleteText = SelectedResults.SelectedItem.QuerySuggestionText;
|
2022-04-12 09:30:04 +00:00
|
|
|
|
}
|
2016-03-26 01:20:42 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
var specialKeyState = GlobalHotkey.CheckModifiers();
|
|
|
|
|
|
if (specialKeyState.ShiftPressed)
|
2022-04-12 09:30:04 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
autoCompleteText = result.SubTitle;
|
2016-04-25 20:25:39 +00:00
|
|
|
|
}
|
2016-02-12 06:21:12 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
ChangeQueryText(autoCompleteText);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-12-23 03:46:12 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
2022-11-22 17:24:35 +00:00
|
|
|
|
private async Task OpenResultAsync(string index)
|
2022-11-19 18:08:32 +00:00
|
|
|
|
{
|
|
|
|
|
|
var results = SelectedResults;
|
2022-11-22 17:24:35 +00:00
|
|
|
|
if (index is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
results.SelectedIndex = int.Parse(index);
|
|
|
|
|
|
}
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
var result = results.SelectedItem?.Result;
|
|
|
|
|
|
if (result == null)
|
2021-12-03 15:31:59 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
var hideWindow = await result.ExecuteAsync(new ActionContext
|
2021-12-03 15:31:59 +00:00
|
|
|
|
{
|
2023-11-19 09:04:29 +00:00
|
|
|
|
// not null means pressing modifier key + number, should ignore the modifier key
|
2023-11-20 15:20:59 +00:00
|
|
|
|
SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers()
|
2022-11-19 18:08:32 +00:00
|
|
|
|
})
|
|
|
|
|
|
.ConfigureAwait(false);
|
2021-12-05 08:15:32 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
if (SelectedIsFromQueryResults())
|
|
|
|
|
|
{
|
|
|
|
|
|
_userSelectedRecord.Add(result);
|
2025-01-26 12:45:58 +00:00
|
|
|
|
// origin query is null when user select the context menu item directly of one item from query list
|
|
|
|
|
|
// so we don't want to add it to history
|
|
|
|
|
|
if (result.OriginQuery != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_history.Add(result.OriginQuery.RawQuery);
|
|
|
|
|
|
}
|
2024-05-19 15:17:57 +00:00
|
|
|
|
lastHistoryIndex = 1;
|
2022-11-19 18:08:32 +00:00
|
|
|
|
}
|
2023-04-03 06:09:36 +00:00
|
|
|
|
|
2023-04-03 05:15:08 +00:00
|
|
|
|
if (hideWindow)
|
|
|
|
|
|
{
|
|
|
|
|
|
Hide();
|
|
|
|
|
|
}
|
2022-11-19 18:08:32 +00:00
|
|
|
|
}
|
2021-12-05 08:15:32 +00:00
|
|
|
|
|
2025-02-20 10:06:15 +00:00
|
|
|
|
private static IReadOnlyList<Result> DeepCloneResults(IReadOnlyList<Result> results, CancellationToken token = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
var resultsCopy = new List<Result>();
|
|
|
|
|
|
foreach (var result in results.ToList())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (token.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var resultCopy = result.Clone();
|
|
|
|
|
|
resultsCopy.Add(resultCopy);
|
|
|
|
|
|
}
|
|
|
|
|
|
return resultsCopy;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-23 11:09:58 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2023-04-22 06:12:12 +00:00
|
|
|
|
#region BasicCommands
|
|
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void OpenSetting()
|
|
|
|
|
|
{
|
|
|
|
|
|
App.API.OpenSettingDialog();
|
|
|
|
|
|
}
|
2021-12-03 15:31:59 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectHelp()
|
|
|
|
|
|
{
|
2025-02-28 09:03:53 +00:00
|
|
|
|
App.API.OpenUrl("https://www.flowlauncher.com/docs/#/usage-tips");
|
2022-11-19 18:08:32 +00:00
|
|
|
|
}
|
2022-01-19 20:43:00 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectFirstResult()
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults.SelectFirstResult();
|
|
|
|
|
|
}
|
2022-01-20 09:04:48 +00:00
|
|
|
|
|
2024-04-15 17:21:29 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectLastResult()
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults.SelectLastResult();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectPrevPage()
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults.SelectPrevPage();
|
|
|
|
|
|
}
|
2022-01-20 09:04:48 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectNextPage()
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults.SelectNextPage();
|
|
|
|
|
|
}
|
2021-12-03 16:59:40 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectPrevItem()
|
|
|
|
|
|
{
|
2024-05-15 16:11:27 +00:00
|
|
|
|
if (_history.Items.Count > 0
|
2024-05-19 15:27:24 +00:00
|
|
|
|
&& QueryText == string.Empty
|
|
|
|
|
|
&& SelectedIsFromQueryResults())
|
2024-05-15 16:11:27 +00:00
|
|
|
|
{
|
2024-05-19 15:16:56 +00:00
|
|
|
|
lastHistoryIndex = 1;
|
2024-05-15 16:11:27 +00:00
|
|
|
|
ReverseHistory();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults.SelectPrevResult();
|
|
|
|
|
|
}
|
2024-05-27 07:09:22 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
}
|
2016-02-12 09:20:46 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void SelectNextItem()
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults.SelectNextResult();
|
|
|
|
|
|
}
|
2021-02-12 03:45:31 +00:00
|
|
|
|
|
2022-11-19 18:08:32 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void Esc()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!SelectedIsFromQueryResults())
|
2016-06-23 21:17:47 +00:00
|
|
|
|
{
|
2022-11-19 18:08:32 +00:00
|
|
|
|
SelectedResults = Results;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2021-02-12 03:45:31 +00:00
|
|
|
|
{
|
2021-10-14 00:00:58 +00:00
|
|
|
|
Hide();
|
2022-11-19 18:08:32 +00:00
|
|
|
|
}
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
2021-11-05 17:31:05 +00:00
|
|
|
|
|
2024-11-21 11:18:05 +00:00
|
|
|
|
public void BackToQueryResults()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!SelectedIsFromQueryResults())
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults = Results;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-23 06:14:53 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
public void ToggleGameMode()
|
|
|
|
|
|
{
|
|
|
|
|
|
GameModeStatus = !GameModeStatus;
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
2024-05-27 07:09:22 +00:00
|
|
|
|
|
2024-03-18 10:11:25 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
public void CopyAlternative()
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = Results.SelectedItem?.Result?.CopyText;
|
|
|
|
|
|
|
|
|
|
|
|
if (result != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
App.API.CopyToClipboard(result, directCopy: false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region ViewModel Properties
|
|
|
|
|
|
|
2022-10-12 04:01:50 +00:00
|
|
|
|
public Settings Settings { get; }
|
2022-11-19 09:54:34 +00:00
|
|
|
|
public string ClockText { get; private set; }
|
2022-10-12 04:01:50 +00:00
|
|
|
|
public string DateText { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
private async Task RegisterClockAndDateUpdateAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
|
|
|
|
|
|
// ReSharper disable once MethodSupportsCancellation
|
|
|
|
|
|
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Settings.UseClock)
|
2024-05-25 06:03:14 +00:00
|
|
|
|
ClockText = DateTime.Now.ToString(Settings.TimeFormat, CultureInfo.CurrentCulture);
|
2022-10-12 04:01:50 +00:00
|
|
|
|
if (Settings.UseDate)
|
2024-05-25 06:03:14 +00:00
|
|
|
|
DateText = DateTime.Now.ToString(Settings.DateFormat, CultureInfo.CurrentCulture);
|
2022-10-12 04:01:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-11-24 07:29:05 +00:00
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
public ResultsViewModel Results { get; private set; }
|
2022-03-01 19:29:21 +00:00
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
public ResultsViewModel ContextMenu { get; private set; }
|
2022-03-01 19:29:21 +00:00
|
|
|
|
|
2016-06-23 21:17:47 +00:00
|
|
|
|
public ResultsViewModel History { get; private set; }
|
2016-05-05 15:08:44 +00:00
|
|
|
|
|
2022-12-23 06:14:53 +00:00
|
|
|
|
public bool GameModeStatus { get; set; } = false;
|
2021-11-19 06:43:47 +00:00
|
|
|
|
|
2016-06-23 21:17:47 +00:00
|
|
|
|
private string _queryText;
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
public string QueryText
|
|
|
|
|
|
{
|
2021-01-17 10:05:31 +00:00
|
|
|
|
get => _queryText;
|
2016-05-05 15:08:44 +00:00
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_queryText = value;
|
2022-12-11 17:00:48 +00:00
|
|
|
|
OnPropertyChanged();
|
2016-06-23 21:17:47 +00:00
|
|
|
|
Query();
|
2016-05-05 15:08:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-05-04 22:41:24 +00:00
|
|
|
|
|
2022-09-19 20:32:17 +00:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void IncreaseWidth()
|
|
|
|
|
|
{
|
2024-05-25 05:36:20 +00:00
|
|
|
|
Settings.WindowSize += 100;
|
2024-06-30 09:03:25 +00:00
|
|
|
|
Settings.WindowLeft -= 50;
|
2024-05-28 08:46:28 +00:00
|
|
|
|
OnPropertyChanged(nameof(MainWindowWidth));
|
2022-09-19 20:32:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void DecreaseWidth()
|
|
|
|
|
|
{
|
2024-06-30 09:03:25 +00:00
|
|
|
|
if (MainWindowWidth - 100 < 400 || Settings.WindowSize == 400)
|
2022-10-13 04:57:42 +00:00
|
|
|
|
{
|
2022-10-25 12:45:06 +00:00
|
|
|
|
Settings.WindowSize = 400;
|
2022-10-13 04:57:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2022-10-25 12:45:06 +00:00
|
|
|
|
{
|
2024-06-30 09:03:25 +00:00
|
|
|
|
Settings.WindowLeft += 50;
|
2022-10-25 12:45:06 +00:00
|
|
|
|
Settings.WindowSize -= 100;
|
2022-10-13 04:57:42 +00:00
|
|
|
|
}
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2024-05-28 08:46:28 +00:00
|
|
|
|
OnPropertyChanged(nameof(MainWindowWidth));
|
2022-09-19 20:32:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void IncreaseMaxResult()
|
|
|
|
|
|
{
|
2022-10-25 12:45:06 +00:00
|
|
|
|
if (Settings.MaxResultsToShow == 17)
|
2022-10-14 09:46:53 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
2022-10-25 12:45:06 +00:00
|
|
|
|
Settings.MaxResultsToShow += 1;
|
2022-09-19 20:32:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void DecreaseMaxResult()
|
|
|
|
|
|
{
|
2022-10-25 12:45:06 +00:00
|
|
|
|
if (Settings.MaxResultsToShow == 2)
|
2022-10-14 09:46:53 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
2022-10-25 12:45:06 +00:00
|
|
|
|
Settings.MaxResultsToShow -= 1;
|
2022-09-19 20:32:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-20 22:37:06 +00:00
|
|
|
|
/// <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
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="queryText"></param>
|
2023-06-11 13:19:41 +00:00
|
|
|
|
/// <param name="isReQuery">Force query even when Query Text doesn't change</param>
|
|
|
|
|
|
public void ChangeQueryText(string queryText, bool isReQuery = false)
|
2016-07-20 22:37:06 +00:00
|
|
|
|
{
|
2022-12-11 17:08:23 +00:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
2021-09-25 01:05:55 +00:00
|
|
|
|
{
|
2025-02-22 11:32:08 +00:00
|
|
|
|
BackToQueryResults();
|
|
|
|
|
|
|
2022-12-11 17:08:23 +00:00
|
|
|
|
if (QueryText != queryText)
|
2022-12-11 17:00:48 +00:00
|
|
|
|
{
|
|
|
|
|
|
// re-query is done in QueryText's setter method
|
|
|
|
|
|
QueryText = queryText;
|
|
|
|
|
|
// set to false so the subsequent set true triggers
|
|
|
|
|
|
// PropertyChanged and MoveQueryTextToEnd is called
|
|
|
|
|
|
QueryTextCursorMovedToEnd = false;
|
2022-12-11 17:08:23 +00:00
|
|
|
|
}
|
2023-06-11 13:19:41 +00:00
|
|
|
|
else if (isReQuery)
|
2022-12-11 17:08:23 +00:00
|
|
|
|
{
|
2023-06-11 13:19:41 +00:00
|
|
|
|
Query(isReQuery: true);
|
2022-12-11 17:08:23 +00:00
|
|
|
|
}
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2022-12-11 17:08:23 +00:00
|
|
|
|
QueryTextCursorMovedToEnd = true;
|
|
|
|
|
|
});
|
2016-07-20 22:37:06 +00:00
|
|
|
|
}
|
2021-01-09 04:01:08 +00:00
|
|
|
|
|
2017-02-12 22:34:12 +00:00
|
|
|
|
public bool LastQuerySelected { get; set; }
|
2021-10-05 08:37:58 +00:00
|
|
|
|
|
|
|
|
|
|
// This is not a reliable indicator of the cursor's position, it is manually set for a specific purpose.
|
2016-07-20 22:37:06 +00:00
|
|
|
|
public bool QueryTextCursorMovedToEnd { get; set; }
|
2016-05-05 15:08:44 +00:00
|
|
|
|
|
2016-06-22 23:26:57 +00:00
|
|
|
|
private ResultsViewModel _selectedResults;
|
2021-01-09 04:01:08 +00:00
|
|
|
|
|
2016-06-22 23:26:57 +00:00
|
|
|
|
private ResultsViewModel SelectedResults
|
2016-05-05 15:08:44 +00:00
|
|
|
|
{
|
2016-06-22 23:26:57 +00:00
|
|
|
|
get { return _selectedResults; }
|
2016-05-05 15:08:44 +00:00
|
|
|
|
set
|
|
|
|
|
|
{
|
2024-06-03 11:59:14 +00:00
|
|
|
|
var isReturningFromContextMenu = ContextMenuSelected();
|
2016-06-22 23:26:57 +00:00
|
|
|
|
_selectedResults = value;
|
2019-10-23 09:20:33 +00:00
|
|
|
|
if (SelectedIsFromQueryResults())
|
2016-05-05 15:08:44 +00:00
|
|
|
|
{
|
2023-01-09 15:56:48 +00:00
|
|
|
|
ContextMenu.Visibility = Visibility.Collapsed;
|
|
|
|
|
|
History.Visibility = Visibility.Collapsed;
|
2024-06-03 11:59:14 +00:00
|
|
|
|
|
|
|
|
|
|
// QueryText setter (used in ChangeQueryText) runs the query again, resetting the selected
|
|
|
|
|
|
// result from the one that was selected before going into the context menu to the first result.
|
|
|
|
|
|
// The code below correctly restores QueryText and puts the text caret at the end without
|
|
|
|
|
|
// running the query again when returning from the context menu.
|
|
|
|
|
|
if (isReturningFromContextMenu)
|
|
|
|
|
|
{
|
|
|
|
|
|
_queryText = _queryTextBeforeLeaveResults;
|
|
|
|
|
|
OnPropertyChanged(nameof(QueryText));
|
|
|
|
|
|
QueryTextCursorMovedToEnd = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ChangeQueryText(_queryTextBeforeLeaveResults);
|
|
|
|
|
|
}
|
2016-05-05 15:08:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2023-01-09 15:56:48 +00:00
|
|
|
|
Results.Visibility = Visibility.Collapsed;
|
2016-06-23 21:17:47 +00:00
|
|
|
|
_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;
|
|
|
|
|
|
}
|
2016-05-05 15:08:44 +00:00
|
|
|
|
}
|
2021-01-09 04:01:08 +00:00
|
|
|
|
|
2023-01-09 15:56:48 +00:00
|
|
|
|
_selectedResults.Visibility = Visibility.Visible;
|
2016-05-05 15:08:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-23 21:17:38 +00:00
|
|
|
|
public Visibility ProgressBarVisibility { get; set; }
|
|
|
|
|
|
public Visibility MainWindowVisibility { get; set; }
|
2021-11-11 21:20:43 +00:00
|
|
|
|
public double MainWindowOpacity { get; set; } = 1;
|
2021-11-27 02:38:47 +00:00
|
|
|
|
|
|
|
|
|
|
// This is to be used for determining the visibility status of the mainwindow instead of MainWindowVisibility
|
|
|
|
|
|
// because it is more accurate and reliable representation than using Visibility as a condition check
|
|
|
|
|
|
public bool MainWindowVisibilityStatus { get; set; } = true;
|
|
|
|
|
|
|
2023-07-03 21:42:41 +00:00
|
|
|
|
public event VisibilityChangedEventHandler VisibilityChanged;
|
|
|
|
|
|
|
2021-12-17 12:48:29 +00:00
|
|
|
|
public Visibility SearchIconVisibility { get; set; }
|
|
|
|
|
|
|
2022-09-19 20:32:17 +00:00
|
|
|
|
public double MainWindowWidth
|
|
|
|
|
|
{
|
2022-10-25 12:45:06 +00:00
|
|
|
|
get => Settings.WindowSize;
|
2024-05-28 08:44:28 +00:00
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!MainWindowVisibilityStatus) return;
|
|
|
|
|
|
Settings.WindowSize = value;
|
|
|
|
|
|
}
|
2022-09-19 20:32:17 +00:00
|
|
|
|
}
|
2021-10-22 04:31:51 +00:00
|
|
|
|
|
2024-05-14 07:54:15 +00:00
|
|
|
|
public double MainWindowHeight
|
|
|
|
|
|
{
|
|
|
|
|
|
get => Settings.WindowHeightSize;
|
|
|
|
|
|
set => Settings.WindowHeightSize = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public double QueryBoxFontSize
|
|
|
|
|
|
{
|
|
|
|
|
|
get => Settings.QueryBoxFontSize;
|
|
|
|
|
|
set => Settings.QueryBoxFontSize = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public double ItemHeightSize
|
|
|
|
|
|
{
|
|
|
|
|
|
get => Settings.ItemHeightSize;
|
|
|
|
|
|
set => Settings.ItemHeightSize = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public double ResultItemFontSize
|
|
|
|
|
|
{
|
|
|
|
|
|
get => Settings.ResultItemFontSize;
|
|
|
|
|
|
set => Settings.ResultItemFontSize = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public double ResultSubItemFontSize
|
|
|
|
|
|
{
|
|
|
|
|
|
get => Settings.ResultSubItemFontSize;
|
|
|
|
|
|
set => Settings.ResultSubItemFontSize = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-27 13:57:17 +00:00
|
|
|
|
public ImageSource PluginIconSource { get; private set; } = null;
|
|
|
|
|
|
|
2021-12-17 11:23:19 +00:00
|
|
|
|
public string PluginIconPath { get; set; } = null;
|
|
|
|
|
|
|
2022-12-22 14:57:15 +00:00
|
|
|
|
public string OpenResultCommandModifiers => Settings.OpenResultModifiers;
|
2022-01-25 18:14:43 +00:00
|
|
|
|
|
2024-04-13 17:49:57 +00:00
|
|
|
|
public string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotkey)
|
2024-03-20 05:47:14 +00:00
|
|
|
|
{
|
2024-04-13 17:49:57 +00:00
|
|
|
|
try
|
2023-02-25 07:00:19 +00:00
|
|
|
|
{
|
2024-04-13 17:49:57 +00:00
|
|
|
|
var converter = new KeyGestureConverter();
|
|
|
|
|
|
var key = (KeyGesture)converter.ConvertFromString(hotkey);
|
2023-02-25 07:00:19 +00:00
|
|
|
|
}
|
2024-04-13 17:49:57 +00:00
|
|
|
|
catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException)
|
2024-04-13 09:47:37 +00:00
|
|
|
|
{
|
2024-04-13 17:49:57 +00:00
|
|
|
|
return defaultHotkey;
|
2024-04-13 09:47:37 +00:00
|
|
|
|
}
|
2024-04-13 17:49:57 +00:00
|
|
|
|
|
|
|
|
|
|
return hotkey;
|
2024-04-13 09:47:37 +00:00
|
|
|
|
}
|
2024-05-27 07:09:22 +00:00
|
|
|
|
|
2024-04-13 17:49:57 +00:00
|
|
|
|
public string PreviewHotkey => VerifyOrSetDefaultHotkey(Settings.PreviewHotkey, "F1");
|
2024-04-15 13:28:07 +00:00
|
|
|
|
public string AutoCompleteHotkey => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey, "Ctrl+Tab");
|
2024-04-18 12:01:01 +00:00
|
|
|
|
public string AutoCompleteHotkey2 => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey2, "");
|
2024-04-15 12:52:15 +00:00
|
|
|
|
public string SelectNextItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey, "Tab");
|
2024-04-18 12:01:01 +00:00
|
|
|
|
public string SelectNextItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey2, "");
|
2024-04-15 13:28:07 +00:00
|
|
|
|
public string SelectPrevItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey, "Shift+Tab");
|
2024-04-18 12:01:01 +00:00
|
|
|
|
public string SelectPrevItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey2, "");
|
|
|
|
|
|
public string SelectNextPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextPageHotkey, "");
|
|
|
|
|
|
public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, "");
|
2024-04-18 07:35:05 +00:00
|
|
|
|
public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O");
|
2024-04-18 07:01:26 +00:00
|
|
|
|
public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I");
|
2024-05-15 16:11:27 +00:00
|
|
|
|
public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up");
|
|
|
|
|
|
public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down");
|
2024-04-13 09:47:37 +00:00
|
|
|
|
|
2021-01-06 02:34:08 +00:00
|
|
|
|
public string Image => Constant.QueryTextBoxIconImagePath;
|
2016-05-09 22:35:20 +00:00
|
|
|
|
|
2022-12-20 14:35:07 +00:00
|
|
|
|
public bool StartWithEnglishMode => Settings.AlwaysStartEn;
|
2024-12-08 20:33:19 +00:00
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2023-04-22 06:12:12 +00:00
|
|
|
|
#region Preview
|
|
|
|
|
|
|
2024-05-28 11:38:02 +00:00
|
|
|
|
public bool InternalPreviewVisible
|
2023-04-22 06:12:12 +00:00
|
|
|
|
{
|
2024-05-28 11:38:02 +00:00
|
|
|
|
get
|
2023-04-22 06:17:05 +00:00
|
|
|
|
{
|
2024-05-28 11:38:02 +00:00
|
|
|
|
if (ResultAreaColumn == ResultAreaColumnPreviewShown)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
if (ResultAreaColumn == ResultAreaColumnPreviewHidden)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
throw new NotImplementedException("ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value");
|
|
|
|
|
|
#else
|
|
|
|
|
|
Log.Error("MainViewModel", "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible");
|
|
|
|
|
|
#endif
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly int ResultAreaColumnPreviewShown = 1;
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly int ResultAreaColumnPreviewHidden = 3;
|
|
|
|
|
|
|
|
|
|
|
|
public int ResultAreaColumn { get; set; } = ResultAreaColumnPreviewShown;
|
|
|
|
|
|
|
2024-05-28 11:41:59 +00:00
|
|
|
|
// This is not a reliable indicator of whether external preview is visible due to the
|
2024-05-30 11:49:44 +00:00
|
|
|
|
// ability of manually closing/exiting the external preview program which, does not inform flow that
|
2024-05-28 11:41:59 +00:00
|
|
|
|
// preview is no longer available.
|
|
|
|
|
|
public bool ExternalPreviewVisible { get; set; } = false;
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowPreview()
|
|
|
|
|
|
{
|
|
|
|
|
|
var useExternalPreview = PluginManager.UseExternalPreview();
|
|
|
|
|
|
|
2024-06-06 02:36:26 +00:00
|
|
|
|
switch (useExternalPreview)
|
2023-05-20 16:29:56 +00:00
|
|
|
|
{
|
2024-06-06 02:36:26 +00:00
|
|
|
|
case true
|
|
|
|
|
|
when CanExternalPreviewSelectedResult(out var path):
|
|
|
|
|
|
// Internal preview may still be on when user switches to external
|
|
|
|
|
|
if (InternalPreviewVisible)
|
|
|
|
|
|
HideInternalPreview();
|
2024-05-28 11:41:59 +00:00
|
|
|
|
OpenExternalPreview(path);
|
2024-06-06 02:36:26 +00:00
|
|
|
|
break;
|
2024-05-28 11:41:59 +00:00
|
|
|
|
|
2024-06-06 02:36:26 +00:00
|
|
|
|
case true
|
|
|
|
|
|
when !CanExternalPreviewSelectedResult(out var _):
|
|
|
|
|
|
if (ExternalPreviewVisible)
|
|
|
|
|
|
CloseExternalPreview();
|
|
|
|
|
|
ShowInternalPreview();
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case false:
|
|
|
|
|
|
ShowInternalPreview();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2024-05-28 11:41:59 +00:00
|
|
|
|
}
|
2016-05-09 22:35:20 +00:00
|
|
|
|
|
2024-05-28 11:41:59 +00:00
|
|
|
|
private void HidePreview()
|
2024-12-08 20:33:19 +00:00
|
|
|
|
{
|
2024-05-28 11:41:59 +00:00
|
|
|
|
if (PluginManager.UseExternalPreview())
|
|
|
|
|
|
CloseExternalPreview();
|
|
|
|
|
|
|
|
|
|
|
|
if (InternalPreviewVisible)
|
|
|
|
|
|
HideInternalPreview();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void TogglePreview()
|
|
|
|
|
|
{
|
2024-05-31 07:02:54 +00:00
|
|
|
|
if (InternalPreviewVisible || ExternalPreviewVisible)
|
2023-04-22 09:06:38 +00:00
|
|
|
|
{
|
2024-05-31 07:02:54 +00:00
|
|
|
|
HidePreview();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ShowPreview();
|
2023-04-22 09:06:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ToggleInternalPreview()
|
|
|
|
|
|
{
|
2024-05-28 11:41:59 +00:00
|
|
|
|
if (!InternalPreviewVisible)
|
2023-04-22 06:12:12 +00:00
|
|
|
|
{
|
2023-04-22 08:55:27 +00:00
|
|
|
|
ShowInternalPreview();
|
2023-04-22 06:12:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2023-04-22 08:55:27 +00:00
|
|
|
|
HideInternalPreview();
|
2023-04-22 06:12:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-28 11:31:29 +00:00
|
|
|
|
private void OpenExternalPreview(string path, bool sendFailToast = true)
|
2023-04-22 09:06:38 +00:00
|
|
|
|
{
|
2024-05-28 11:31:29 +00:00
|
|
|
|
_ = PluginManager.OpenExternalPreviewAsync(path, sendFailToast).ConfigureAwait(false);
|
|
|
|
|
|
ExternalPreviewVisible = true;
|
2023-05-13 17:40:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-28 11:31:29 +00:00
|
|
|
|
private void CloseExternalPreview()
|
2023-05-13 17:40:45 +00:00
|
|
|
|
{
|
2024-05-28 11:31:29 +00:00
|
|
|
|
_ = PluginManager.CloseExternalPreviewAsync().ConfigureAwait(false);
|
|
|
|
|
|
ExternalPreviewVisible = false;
|
2023-05-13 17:40:45 +00:00
|
|
|
|
}
|
2023-07-11 12:08:22 +00:00
|
|
|
|
|
2024-05-28 11:31:29 +00:00
|
|
|
|
private void SwitchExternalPreview(string path, bool sendFailToast = true)
|
2023-05-13 17:40:45 +00:00
|
|
|
|
{
|
2024-05-28 11:31:29 +00:00
|
|
|
|
_ = PluginManager.SwitchExternalPreviewAsync(path,sendFailToast).ConfigureAwait(false);
|
2023-04-22 09:06:38 +00:00
|
|
|
|
}
|
2020-05-03 17:17:16 +00:00
|
|
|
|
|
2023-04-22 08:55:27 +00:00
|
|
|
|
private void ShowInternalPreview()
|
2023-04-22 06:12:12 +00:00
|
|
|
|
{
|
2024-05-28 11:41:59 +00:00
|
|
|
|
ResultAreaColumn = ResultAreaColumnPreviewShown;
|
2023-04-22 06:12:12 +00:00
|
|
|
|
Results.SelectedItem?.LoadPreviewImage();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-22 08:55:27 +00:00
|
|
|
|
private void HideInternalPreview()
|
2023-04-22 06:12:12 +00:00
|
|
|
|
{
|
2024-05-28 11:41:59 +00:00
|
|
|
|
ResultAreaColumn = ResultAreaColumnPreviewHidden;
|
2023-04-22 06:12:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ResetPreview()
|
|
|
|
|
|
{
|
2024-06-12 04:14:25 +00:00
|
|
|
|
switch (Settings.AlwaysPreview)
|
2023-04-22 06:12:12 +00:00
|
|
|
|
{
|
2024-06-12 04:14:25 +00:00
|
|
|
|
case true
|
|
|
|
|
|
when PluginManager.AllowAlwaysPreview() && CanExternalPreviewSelectedResult(out var path):
|
|
|
|
|
|
OpenExternalPreview(path);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case true:
|
|
|
|
|
|
ShowInternalPreview();
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case false:
|
|
|
|
|
|
HidePreview();
|
|
|
|
|
|
break;
|
2023-04-22 06:12:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-08 20:33:19 +00:00
|
|
|
|
|
2023-04-22 06:12:12 +00:00
|
|
|
|
private void UpdatePreview()
|
|
|
|
|
|
{
|
2024-05-28 11:41:59 +00:00
|
|
|
|
switch (PluginManager.UseExternalPreview())
|
2023-04-22 07:53:43 +00:00
|
|
|
|
{
|
2024-05-28 11:41:59 +00:00
|
|
|
|
case true
|
2024-06-06 02:36:26 +00:00
|
|
|
|
when CanExternalPreviewSelectedResult(out var path):
|
|
|
|
|
|
if (ExternalPreviewVisible)
|
|
|
|
|
|
{
|
|
|
|
|
|
SwitchExternalPreview(path, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (InternalPreviewVisible)
|
|
|
|
|
|
{
|
|
|
|
|
|
HideInternalPreview();
|
|
|
|
|
|
OpenExternalPreview(path);
|
|
|
|
|
|
}
|
2024-05-28 11:41:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case true
|
2024-06-06 02:36:26 +00:00
|
|
|
|
when !CanExternalPreviewSelectedResult(out var _):
|
|
|
|
|
|
if (ExternalPreviewVisible)
|
|
|
|
|
|
{
|
|
|
|
|
|
CloseExternalPreview();
|
|
|
|
|
|
ShowInternalPreview();
|
|
|
|
|
|
}
|
2024-05-28 11:41:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
2024-06-06 02:36:26 +00:00
|
|
|
|
case false
|
|
|
|
|
|
when InternalPreviewVisible:
|
|
|
|
|
|
Results.SelectedItem?.LoadPreviewImage();
|
2024-05-28 11:41:59 +00:00
|
|
|
|
break;
|
2023-04-22 07:53:43 +00:00
|
|
|
|
}
|
2023-04-22 06:17:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-22 07:53:43 +00:00
|
|
|
|
private bool CanExternalPreviewSelectedResult(out string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
path = Results.SelectedItem?.Result?.Preview.FilePath;
|
2023-04-22 09:06:38 +00:00
|
|
|
|
return !string.IsNullOrEmpty(path);
|
2023-04-22 07:53:43 +00:00
|
|
|
|
}
|
2020-05-04 22:41:24 +00:00
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2022-12-23 03:46:12 +00:00
|
|
|
|
#region Query
|
|
|
|
|
|
|
2023-06-11 13:19:41 +00:00
|
|
|
|
public void Query(bool isReQuery = false)
|
2016-06-23 21:17:47 +00:00
|
|
|
|
{
|
2019-10-23 09:20:33 +00:00
|
|
|
|
if (SelectedIsFromQueryResults())
|
2016-06-23 21:17:47 +00:00
|
|
|
|
{
|
2023-06-11 13:19:41 +00:00
|
|
|
|
QueryResults(isReQuery);
|
2016-06-23 21:17:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
else if (ContextMenuSelected())
|
|
|
|
|
|
{
|
|
|
|
|
|
QueryContextMenu();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (HistorySelected())
|
|
|
|
|
|
{
|
|
|
|
|
|
QueryHistory();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2016-02-21 14:40:10 +00:00
|
|
|
|
private void QueryContextMenu()
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2016-06-23 21:17:47 +00:00
|
|
|
|
const string id = "Context Menu ID";
|
2016-06-22 23:26:57 +00:00
|
|
|
|
var query = QueryText.ToLower().Trim();
|
|
|
|
|
|
ContextMenu.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
var selected = Results.SelectedItem?.Result;
|
|
|
|
|
|
|
|
|
|
|
|
if (selected != null) // SelectedItem returns null if selection is empty.
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2024-01-24 00:51:25 +00:00
|
|
|
|
List<Result> results;
|
2025-01-26 12:21:30 +00:00
|
|
|
|
|
2025-01-26 12:45:58 +00:00
|
|
|
|
results = PluginManager.GetContextMenusForPlugin(selected);
|
|
|
|
|
|
results.Add(ContextMenuTopMost(selected));
|
|
|
|
|
|
results.Add(ContextMenuPluginInfo(selected.PluginID));
|
2016-03-26 01:20:42 +00:00
|
|
|
|
|
2016-06-22 23:26:57 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(query))
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2024-02-04 16:41:42 +00:00
|
|
|
|
var filtered = results.Select(x => x.Clone()).Where
|
2016-06-22 23:26:57 +00:00
|
|
|
|
(
|
2021-01-17 10:10:36 +00:00
|
|
|
|
r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var match = StringMatcher.FuzzySearch(query, r.Title);
|
|
|
|
|
|
if (!match.IsSearchPrecisionScoreMet())
|
|
|
|
|
|
{
|
|
|
|
|
|
match = StringMatcher.FuzzySearch(query, r.SubTitle);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!match.IsSearchPrecisionScoreMet()) return false;
|
2021-02-03 09:50:21 +00:00
|
|
|
|
|
2021-01-17 10:10:36 +00:00
|
|
|
|
r.Score = match.Score;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}).ToList();
|
2016-06-23 21:17:47 +00:00
|
|
|
|
ContextMenu.AddResults(filtered, id);
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
2016-06-22 23:26:57 +00:00
|
|
|
|
else
|
2016-05-05 20:15:13 +00:00
|
|
|
|
{
|
2016-06-23 21:17:47 +00:00
|
|
|
|
ContextMenu.AddResults(results, id);
|
2016-06-22 23:26:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-23 21:17:47 +00:00
|
|
|
|
private void QueryHistory()
|
2016-06-22 23:26:57 +00:00
|
|
|
|
{
|
2016-06-23 21:17:47 +00:00
|
|
|
|
const string id = "Query History ID";
|
|
|
|
|
|
var query = QueryText.ToLower().Trim();
|
|
|
|
|
|
History.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
var results = new List<Result>();
|
|
|
|
|
|
foreach (var h in _history.Items)
|
2016-06-22 23:26:57 +00:00
|
|
|
|
{
|
2016-06-23 21:17:47 +00:00
|
|
|
|
var title = _translator.GetTranslation("executeQuery");
|
|
|
|
|
|
var time = _translator.GetTranslation("lastExecuteTime");
|
|
|
|
|
|
var result = new Result
|
|
|
|
|
|
{
|
|
|
|
|
|
Title = string.Format(title, h.Query),
|
|
|
|
|
|
SubTitle = string.Format(time, h.ExecutedDateTime),
|
|
|
|
|
|
IcoPath = "Images\\history.png",
|
2024-03-20 05:47:14 +00:00
|
|
|
|
OriginQuery = new Query { RawQuery = h.Query },
|
2016-06-23 21:17:47 +00:00
|
|
|
|
Action = _ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults = Results;
|
2016-07-20 22:37:06 +00:00
|
|
|
|
ChangeQueryText(h.Query);
|
2016-06-23 21:17:47 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
results.Add(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(query))
|
|
|
|
|
|
{
|
|
|
|
|
|
var filtered = results.Where
|
|
|
|
|
|
(
|
2019-09-29 05:17:40 +00:00
|
|
|
|
r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() ||
|
|
|
|
|
|
StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet()
|
2016-06-23 21:17:47 +00:00
|
|
|
|
).ToList();
|
|
|
|
|
|
History.AddResults(filtered, id);
|
2016-06-22 23:26:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-06-23 21:17:47 +00:00
|
|
|
|
History.AddResults(results, id);
|
2016-02-18 11:30:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
private readonly IReadOnlyList<Result> _emptyResult = new List<Result>();
|
2021-07-07 17:38:57 +00:00
|
|
|
|
|
2024-03-20 17:26:39 +00:00
|
|
|
|
private async void QueryResults(bool isReQuery = false, bool reSelect = true)
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2021-01-13 01:25:46 +00:00
|
|
|
|
_updateSource?.Cancel();
|
2016-02-18 11:30:36 +00:00
|
|
|
|
|
2022-11-08 04:35:28 +00:00
|
|
|
|
var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
|
|
|
|
|
|
|
|
|
|
|
|
if (query == null) // shortcut expanded
|
2016-02-18 11:30:36 +00:00
|
|
|
|
{
|
2021-01-13 01:25:46 +00:00
|
|
|
|
Results.Clear();
|
2023-01-09 15:56:48 +00:00
|
|
|
|
Results.Visibility = Visibility.Collapsed;
|
2021-12-17 11:33:26 +00:00
|
|
|
|
PluginIconPath = null;
|
2024-11-27 13:57:17 +00:00
|
|
|
|
PluginIconSource = null;
|
2021-12-17 12:48:29 +00:00
|
|
|
|
SearchIconVisibility = Visibility.Visible;
|
2021-01-13 01:25:46 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2016-04-25 00:10:55 +00:00
|
|
|
|
|
2021-01-13 01:25:46 +00:00
|
|
|
|
_updateSource?.Dispose();
|
2020-11-20 15:21:04 +00:00
|
|
|
|
|
2021-01-13 01:25:46 +00:00
|
|
|
|
var currentUpdateSource = new CancellationTokenSource();
|
|
|
|
|
|
_updateSource = currentUpdateSource;
|
|
|
|
|
|
var currentCancellationToken = _updateSource.Token;
|
|
|
|
|
|
_updateToken = currentCancellationToken;
|
2020-11-21 08:42:15 +00:00
|
|
|
|
|
2021-01-13 01:25:46 +00:00
|
|
|
|
ProgressBarVisibility = Visibility.Hidden;
|
|
|
|
|
|
_isQueryRunning = true;
|
2021-01-02 07:52:41 +00:00
|
|
|
|
|
2021-06-25 06:43:47 +00:00
|
|
|
|
// Switch to ThreadPool thread
|
|
|
|
|
|
await TaskScheduler.Default;
|
|
|
|
|
|
|
2021-06-26 07:42:27 +00:00
|
|
|
|
if (currentCancellationToken.IsCancellationRequested)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2023-06-07 20:19:47 +00:00
|
|
|
|
// Update the query's IsReQuery property to true if this is a re-query
|
2023-06-11 13:19:41 +00:00
|
|
|
|
query.IsReQuery = isReQuery;
|
2021-01-09 04:17:02 +00:00
|
|
|
|
|
2021-01-13 01:25:46 +00:00
|
|
|
|
// handle the exclusiveness of plugin using action keyword
|
|
|
|
|
|
RemoveOldQueryResults(query);
|
2021-01-02 07:52:41 +00:00
|
|
|
|
|
2021-01-13 01:25:46 +00:00
|
|
|
|
_lastQuery = query;
|
|
|
|
|
|
|
|
|
|
|
|
var plugins = PluginManager.ValidPluginsForQuery(query);
|
2020-11-11 10:36:01 +00:00
|
|
|
|
|
2021-12-17 12:48:29 +00:00
|
|
|
|
if (plugins.Count == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
PluginIconPath = plugins.Single().Metadata.IcoPath;
|
2024-11-27 13:57:17 +00:00
|
|
|
|
PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath);
|
2021-12-17 12:48:29 +00:00
|
|
|
|
SearchIconVisibility = Visibility.Hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
PluginIconPath = null;
|
2024-11-27 13:57:17 +00:00
|
|
|
|
PluginIconSource = null;
|
2021-12-17 12:48:29 +00:00
|
|
|
|
SearchIconVisibility = Visibility.Visible;
|
|
|
|
|
|
}
|
2022-10-12 04:01:50 +00:00
|
|
|
|
|
2021-12-17 11:23:19 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Wait 45 millisecond for query change in global query
|
|
|
|
|
|
// if query changes, return so that it won't be calculated
|
|
|
|
|
|
await Task.Delay(45, currentCancellationToken);
|
|
|
|
|
|
if (currentCancellationToken.IsCancellationRequested)
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_ = Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
|
|
|
|
|
|
if (!currentCancellationToken.IsCancellationRequested && _isQueryRunning)
|
2021-01-17 09:51:21 +00:00
|
|
|
|
{
|
2021-06-11 04:40:07 +00:00
|
|
|
|
ProgressBarVisibility = Visibility.Visible;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, currentCancellationToken, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
|
2019-12-03 22:15:46 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
// plugins is ICollection, meaning LINQ will get the Count and preallocate Array
|
2021-01-02 07:52:41 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
|
|
|
|
|
|
{
|
2024-03-20 17:26:39 +00:00
|
|
|
|
false => QueryTask(plugin, reSelect),
|
2021-06-11 04:40:07 +00:00
|
|
|
|
true => Task.CompletedTask
|
|
|
|
|
|
}).ToArray();
|
2021-05-28 09:06:58 +00:00
|
|
|
|
|
2019-12-03 22:15:46 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
|
|
|
|
|
|
await Task.WhenAll(tasks);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// nothing to do here
|
|
|
|
|
|
}
|
2021-01-10 03:13:46 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
if (currentCancellationToken.IsCancellationRequested)
|
|
|
|
|
|
return;
|
2021-01-17 10:05:31 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
// this should happen once after all queries are done so progress bar should continue
|
|
|
|
|
|
// until the end of all querying
|
|
|
|
|
|
_isQueryRunning = false;
|
|
|
|
|
|
if (!currentCancellationToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
// update to hidden if this is still the current query
|
|
|
|
|
|
ProgressBarVisibility = Visibility.Hidden;
|
|
|
|
|
|
}
|
2021-01-17 09:51:21 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
// Local function
|
2024-03-20 17:26:39 +00:00
|
|
|
|
async Task QueryTask(PluginPair plugin, bool reSelect = true)
|
2021-06-11 04:40:07 +00:00
|
|
|
|
{
|
2021-06-25 06:43:47 +00:00
|
|
|
|
// Since it is wrapped within a ThreadPool Thread, the synchronous context is null
|
2021-06-11 04:40:07 +00:00
|
|
|
|
// Task.Yield will force it to run in ThreadPool
|
|
|
|
|
|
await Task.Yield();
|
2021-01-17 09:51:21 +00:00
|
|
|
|
|
2024-03-20 05:47:14 +00:00
|
|
|
|
IReadOnlyList<Result> results =
|
|
|
|
|
|
await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken);
|
2021-07-07 17:38:57 +00:00
|
|
|
|
|
2021-06-11 04:40:07 +00:00
|
|
|
|
currentCancellationToken.ThrowIfCancellationRequested();
|
2021-04-19 05:06:03 +00:00
|
|
|
|
|
2025-02-20 10:17:29 +00:00
|
|
|
|
IReadOnlyList<Result> resultsCopy;
|
|
|
|
|
|
if (results == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
resultsCopy = _emptyResult;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// make a copy of results to avoid possible issue that FL changes some properties of the records, like score, etc.
|
|
|
|
|
|
resultsCopy = DeepCloneResults(results);
|
|
|
|
|
|
}
|
2021-06-11 04:40:07 +00:00
|
|
|
|
|
2025-02-20 10:17:29 +00:00
|
|
|
|
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query,
|
2024-03-20 17:26:39 +00:00
|
|
|
|
currentCancellationToken, reSelect)))
|
2021-06-11 04:40:07 +00:00
|
|
|
|
{
|
|
|
|
|
|
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-12 06:21:12 +00:00
|
|
|
|
}
|
2016-06-23 21:17:47 +00:00
|
|
|
|
|
2024-03-20 05:47:14 +00:00
|
|
|
|
private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel> customShortcuts,
|
|
|
|
|
|
IEnumerable<BuiltinShortcutModel> builtInShortcuts)
|
2022-06-04 05:30:33 +00:00
|
|
|
|
{
|
2022-11-08 04:35:28 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(queryText))
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-04 05:30:33 +00:00
|
|
|
|
StringBuilder queryBuilder = new(queryText);
|
2022-11-07 18:00:34 +00:00
|
|
|
|
StringBuilder queryBuilderTmp = new(queryText);
|
2022-06-04 05:30:33 +00:00
|
|
|
|
|
2024-03-27 02:56:10 +00:00
|
|
|
|
// Sorting order is important here, the reason is for matching longest shortcut by default
|
2024-02-24 18:10:25 +00:00
|
|
|
|
foreach (var shortcut in customShortcuts.OrderByDescending(x => x.Key.Length))
|
2022-06-04 05:30:33 +00:00
|
|
|
|
{
|
2022-10-13 11:49:44 +00:00
|
|
|
|
if (queryBuilder.Equals(shortcut.Key))
|
2022-06-04 05:30:33 +00:00
|
|
|
|
{
|
2022-10-13 11:49:44 +00:00
|
|
|
|
queryBuilder.Replace(shortcut.Key, shortcut.Expand());
|
2022-06-04 05:30:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-13 11:49:44 +00:00
|
|
|
|
queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand());
|
|
|
|
|
|
}
|
2022-12-11 17:00:48 +00:00
|
|
|
|
|
2023-01-12 09:06:23 +00:00
|
|
|
|
string customExpanded = queryBuilder.ToString();
|
|
|
|
|
|
|
2022-12-08 03:13:07 +00:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
2022-10-13 11:49:44 +00:00
|
|
|
|
{
|
2022-12-08 03:13:07 +00:00
|
|
|
|
foreach (var shortcut in builtInShortcuts)
|
|
|
|
|
|
{
|
2022-12-22 13:22:56 +00:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2023-01-12 09:06:23 +00:00
|
|
|
|
if (customExpanded.Contains(shortcut.Key))
|
|
|
|
|
|
{
|
|
|
|
|
|
var expansion = shortcut.Expand();
|
|
|
|
|
|
queryBuilder.Replace(shortcut.Key, expansion);
|
|
|
|
|
|
queryBuilderTmp.Replace(shortcut.Key, expansion);
|
|
|
|
|
|
}
|
2022-12-22 13:22:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2024-03-20 05:47:14 +00:00
|
|
|
|
Log.Exception(
|
|
|
|
|
|
$"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}",
|
|
|
|
|
|
e);
|
2022-12-22 13:22:56 +00:00
|
|
|
|
}
|
2022-12-08 03:13:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2022-06-04 05:30:33 +00:00
|
|
|
|
|
2022-11-07 18:00:34 +00:00
|
|
|
|
// show expanded builtin shortcuts
|
|
|
|
|
|
// use private field to avoid infinite recursion
|
|
|
|
|
|
_queryText = queryBuilderTmp.ToString();
|
|
|
|
|
|
|
2022-06-04 05:30:33 +00:00
|
|
|
|
var query = QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
|
|
|
|
|
|
return query;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-12-13 22:17:05 +00:00
|
|
|
|
private void RemoveOldQueryResults(Query query)
|
|
|
|
|
|
{
|
2022-07-21 10:12:24 +00:00
|
|
|
|
if (_lastQuery?.ActionKeyword != query?.ActionKeyword)
|
2019-12-13 22:17:05 +00:00
|
|
|
|
{
|
2021-06-06 06:27:10 +00:00
|
|
|
|
Results.Clear();
|
2019-12-13 22:17:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 20:25:39 +00:00
|
|
|
|
private Result ContextMenuTopMost(Result result)
|
2016-03-26 01:20:42 +00:00
|
|
|
|
{
|
2016-04-25 20:25:39 +00:00
|
|
|
|
Result menu;
|
2016-03-28 00:09:40 +00:00
|
|
|
|
if (_topMostRecord.IsTopMost(result))
|
2016-03-26 01:20:42 +00:00
|
|
|
|
{
|
2016-04-27 01:15:53 +00:00
|
|
|
|
menu = new Result
|
2016-03-26 01:20:42 +00:00
|
|
|
|
{
|
2016-04-27 01:15:53 +00:00
|
|
|
|
Title = InternationalizationManager.Instance.GetTranslation("cancelTopMostInThisQuery"),
|
|
|
|
|
|
IcoPath = "Images\\down.png",
|
2016-06-22 23:26:57 +00:00
|
|
|
|
PluginDirectory = Constant.ProgramDirectory,
|
2016-03-26 01:20:42 +00:00
|
|
|
|
Action = _ =>
|
|
|
|
|
|
{
|
2016-03-28 00:09:40 +00:00
|
|
|
|
_topMostRecord.Remove(result);
|
2022-01-10 22:20:55 +00:00
|
|
|
|
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
|
2025-01-26 12:07:40 +00:00
|
|
|
|
App.API.ReQuery();
|
2016-03-26 01:20:42 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-04-27 01:15:53 +00:00
|
|
|
|
menu = new Result
|
2016-03-26 01:20:42 +00:00
|
|
|
|
{
|
2016-04-27 01:15:53 +00:00
|
|
|
|
Title = InternationalizationManager.Instance.GetTranslation("setAsTopMostInThisQuery"),
|
|
|
|
|
|
IcoPath = "Images\\up.png",
|
2021-10-07 10:58:54 +00:00
|
|
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xeac2"),
|
2016-06-22 23:26:57 +00:00
|
|
|
|
PluginDirectory = Constant.ProgramDirectory,
|
2016-03-26 01:20:42 +00:00
|
|
|
|
Action = _ =>
|
|
|
|
|
|
{
|
2016-03-28 00:09:40 +00:00
|
|
|
|
_topMostRecord.AddOrUpdate(result);
|
2022-12-08 22:05:30 +00:00
|
|
|
|
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
|
2025-01-26 12:07:40 +00:00
|
|
|
|
App.API.ReQuery();
|
2016-03-26 01:20:42 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2021-01-09 04:01:08 +00:00
|
|
|
|
|
2016-04-25 20:25:39 +00:00
|
|
|
|
return menu;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Result ContextMenuPluginInfo(string id)
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = PluginManager.GetPluginForId(id).Metadata;
|
|
|
|
|
|
var translator = InternationalizationManager.Instance;
|
|
|
|
|
|
|
|
|
|
|
|
var author = translator.GetTranslation("author");
|
|
|
|
|
|
var website = translator.GetTranslation("website");
|
|
|
|
|
|
var version = translator.GetTranslation("version");
|
|
|
|
|
|
var plugin = translator.GetTranslation("plugin");
|
|
|
|
|
|
var title = $"{plugin}: {metadata.Name}";
|
|
|
|
|
|
var icon = metadata.IcoPath;
|
2021-12-06 10:10:34 +00:00
|
|
|
|
var subtitle = $"{author} {metadata.Author}";
|
2016-04-25 20:25:39 +00:00
|
|
|
|
|
2016-04-27 01:15:53 +00:00
|
|
|
|
var menu = new Result
|
2016-04-25 20:25:39 +00:00
|
|
|
|
{
|
2016-04-27 01:15:53 +00:00
|
|
|
|
Title = title,
|
|
|
|
|
|
IcoPath = icon,
|
|
|
|
|
|
SubTitle = subtitle,
|
2016-04-25 20:25:39 +00:00
|
|
|
|
PluginDirectory = metadata.PluginDirectory,
|
2022-01-23 21:16:17 +00:00
|
|
|
|
Action = _ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
App.API.OpenUrl(metadata.Website);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2016-04-25 20:25:39 +00:00
|
|
|
|
};
|
|
|
|
|
|
return menu;
|
2016-03-26 01:20:42 +00:00
|
|
|
|
}
|
2016-05-09 22:35:20 +00:00
|
|
|
|
|
2021-10-05 08:37:58 +00:00
|
|
|
|
internal bool SelectedIsFromQueryResults()
|
2016-06-22 23:26:57 +00:00
|
|
|
|
{
|
|
|
|
|
|
var selected = SelectedResults == Results;
|
|
|
|
|
|
return selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-23 21:17:47 +00:00
|
|
|
|
private bool ContextMenuSelected()
|
|
|
|
|
|
{
|
|
|
|
|
|
var selected = SelectedResults == ContextMenu;
|
|
|
|
|
|
return selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool HistorySelected()
|
|
|
|
|
|
{
|
|
|
|
|
|
var selected = SelectedResults == History;
|
|
|
|
|
|
return selected;
|
|
|
|
|
|
}
|
2021-01-09 04:01:08 +00:00
|
|
|
|
|
2022-12-23 03:46:12 +00:00
|
|
|
|
#endregion
|
2016-05-09 22:35:20 +00:00
|
|
|
|
|
|
|
|
|
|
#region Hotkey
|
2020-05-03 17:17:16 +00:00
|
|
|
|
|
2021-11-05 17:31:05 +00:00
|
|
|
|
public void ToggleFlowLauncher()
|
2016-05-09 22:35:20 +00:00
|
|
|
|
{
|
2021-11-27 02:38:47 +00:00
|
|
|
|
if (!MainWindowVisibilityStatus)
|
2016-05-09 22:35:20 +00:00
|
|
|
|
{
|
2021-11-14 08:45:56 +00:00
|
|
|
|
Show();
|
2016-05-09 22:35:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2021-10-31 19:35:58 +00:00
|
|
|
|
Hide();
|
2016-05-09 22:35:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-11-27 02:38:47 +00:00
|
|
|
|
|
2021-11-14 08:45:56 +00:00
|
|
|
|
public void Show()
|
|
|
|
|
|
{
|
2022-12-08 03:13:07 +00:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
2025-03-06 05:57:02 +00:00
|
|
|
|
if (Application.Current.MainWindow is MainWindow mainWindow)
|
|
|
|
|
|
{
|
2025-03-16 09:48:22 +00:00
|
|
|
|
// 📌 DWM Cloak 해제 (창을 정상적으로 표시)
|
|
|
|
|
|
Win32Helper.DWMSetCloakForWindow(mainWindow, false);
|
2025-03-08 07:12:57 +00:00
|
|
|
|
|
|
|
|
|
|
// 📌 UI 요소 복원
|
2025-03-06 11:02:27 +00:00
|
|
|
|
mainWindow.ClockPanel.Visibility = Visibility.Visible;
|
2025-03-16 09:39:53 +00:00
|
|
|
|
//mainWindow.SearchIcon.Visibility = Visibility.Visible;
|
2025-03-11 02:55:41 +00:00
|
|
|
|
SearchIconVisibility = Visibility.Visible;
|
2025-03-06 05:57:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WPF 속성 업데이트
|
2025-03-08 07:12:57 +00:00
|
|
|
|
MainWindowVisibility = Visibility.Visible;
|
2025-03-08 09:41:30 +00:00
|
|
|
|
MainWindowOpacity = 1;
|
2022-12-11 17:00:48 +00:00
|
|
|
|
MainWindowVisibilityStatus = true;
|
2023-07-03 21:42:41 +00:00
|
|
|
|
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true });
|
2022-12-08 03:13:07 +00:00
|
|
|
|
});
|
2021-11-14 08:45:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-06 05:57:02 +00:00
|
|
|
|
public async void Hide()
|
2021-10-14 00:00:58 +00:00
|
|
|
|
{
|
2025-03-06 05:14:25 +00:00
|
|
|
|
lastHistoryIndex = 1;
|
2025-03-06 05:57:02 +00:00
|
|
|
|
|
2024-05-28 11:41:59 +00:00
|
|
|
|
if (ExternalPreviewVisible)
|
|
|
|
|
|
CloseExternalPreview();
|
2023-04-22 09:16:02 +00:00
|
|
|
|
|
2023-04-03 06:09:36 +00:00
|
|
|
|
if (!SelectedIsFromQueryResults())
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedResults = Results;
|
|
|
|
|
|
}
|
2024-03-20 05:47:14 +00:00
|
|
|
|
|
2025-03-06 11:18:12 +00:00
|
|
|
|
if (Application.Current.MainWindow is MainWindow mainWindow)
|
|
|
|
|
|
{
|
2025-03-16 09:39:53 +00:00
|
|
|
|
// 📌 아이콘과 시계 Opacity를 0으로 설정하고 Visibility.Hidden 적용
|
2025-03-06 11:18:12 +00:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
mainWindow.ClockPanel.Opacity = 0;
|
|
|
|
|
|
mainWindow.SearchIcon.Opacity = 0;
|
|
|
|
|
|
mainWindow.ClockPanel.Visibility = Visibility.Hidden;
|
2025-03-16 09:39:53 +00:00
|
|
|
|
//mainWindow.SearchIcon.Visibility = Visibility.Hidden;
|
2025-03-11 02:55:41 +00:00
|
|
|
|
SearchIconVisibility = Visibility.Hidden;
|
2025-03-06 11:18:12 +00:00
|
|
|
|
|
|
|
|
|
|
// 강제 UI 업데이트
|
|
|
|
|
|
mainWindow.ClockPanel.UpdateLayout();
|
|
|
|
|
|
mainWindow.SearchIcon.UpdateLayout();
|
|
|
|
|
|
}, DispatcherPriority.Render);
|
|
|
|
|
|
|
2025-03-16 09:48:22 +00:00
|
|
|
|
// 📌 DWM Cloak 적용 (창을 완전히 숨김)
|
|
|
|
|
|
Win32Helper.DWMSetCloakForWindow(mainWindow, true);
|
2025-03-06 11:18:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-06 05:57:02 +00:00
|
|
|
|
// 📌 텍스트 초기화 즉시 적용 + UI 강제 업데이트
|
|
|
|
|
|
if (Settings.LastQueryMode == LastQueryMode.Empty)
|
2021-10-14 00:00:58 +00:00
|
|
|
|
{
|
2025-03-06 05:57:02 +00:00
|
|
|
|
ChangeQueryText(string.Empty);
|
|
|
|
|
|
await Task.Delay(1); // 한 프레임 후 UI가 반영되도록 대기
|
|
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
2025-03-06 04:33:36 +00:00
|
|
|
|
{
|
2025-03-06 05:57:02 +00:00
|
|
|
|
Application.Current.MainWindow.UpdateLayout(); // UI 강제 업데이트
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-03-06 04:33:36 +00:00
|
|
|
|
|
2025-03-06 05:57:02 +00:00
|
|
|
|
switch (Settings.LastQueryMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
case LastQueryMode.Preserved:
|
|
|
|
|
|
case LastQueryMode.Selected:
|
|
|
|
|
|
LastQuerySelected = (Settings.LastQueryMode == LastQueryMode.Preserved);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case LastQueryMode.ActionKeywordPreserved:
|
|
|
|
|
|
case LastQueryMode.ActionKeywordSelected:
|
|
|
|
|
|
var newQuery = _lastQuery.ActionKeyword;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(newQuery))
|
|
|
|
|
|
newQuery += " ";
|
|
|
|
|
|
ChangeQueryText(newQuery);
|
2025-03-06 04:33:36 +00:00
|
|
|
|
|
2025-03-06 05:57:02 +00:00
|
|
|
|
if (Settings.LastQueryMode == LastQueryMode.ActionKeywordSelected)
|
2025-03-06 04:33:36 +00:00
|
|
|
|
LastQuerySelected = false;
|
2025-03-06 05:57:02 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-11-27 02:38:47 +00:00
|
|
|
|
|
2025-03-16 09:39:53 +00:00
|
|
|
|
//if (Application.Current.MainWindow is MainWindow mainWindow2)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// IntPtr hWnd = new WindowInteropHelper(mainWindow2).Handle;
|
|
|
|
|
|
|
|
|
|
|
|
// // 📌 Cloak을 사용하지 않고 일반적인 `ShowWindow(SW_HIDE)` 사용 → Mica/Acrylic 유지됨
|
|
|
|
|
|
// ShowWindow(hWnd, SW_HIDE);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
2025-03-06 05:57:02 +00:00
|
|
|
|
// WPF 속성 업데이트
|
2025-02-24 06:31:41 +00:00
|
|
|
|
MainWindowVisibilityStatus = false;
|
2025-03-08 07:12:57 +00:00
|
|
|
|
MainWindowVisibility = Visibility.Collapsed;
|
2023-07-03 21:42:41 +00:00
|
|
|
|
VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = false });
|
2016-05-09 22:35:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-14 00:00:58 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks if Flow Launcher should ignore any hotkeys
|
|
|
|
|
|
/// </summary>
|
2021-10-20 03:10:37 +00:00
|
|
|
|
public bool ShouldIgnoreHotkeys()
|
2021-10-14 00:00:58 +00:00
|
|
|
|
{
|
2025-03-16 14:24:06 +00:00
|
|
|
|
return Settings.IgnoreHotkeysOnFullscreen && Win32Helper.IsForegroundWindowFullscreen() || GameModeStatus;
|
2021-10-14 00:00:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-23 03:46:12 +00:00
|
|
|
|
#endregion
|
2021-10-14 00:00:58 +00:00
|
|
|
|
|
2016-02-18 11:30:36 +00:00
|
|
|
|
#region Public Methods
|
|
|
|
|
|
|
2016-05-05 15:08:44 +00:00
|
|
|
|
public void Save()
|
|
|
|
|
|
{
|
2021-04-19 05:06:03 +00:00
|
|
|
|
_historyItemsStorage.Save();
|
|
|
|
|
|
_userSelectedRecordStorage.Save();
|
|
|
|
|
|
_topMostRecordStorage.Save();
|
2016-05-05 15:08:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-11 11:06:05 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// To avoid deadlock, this method should not called from main thread
|
|
|
|
|
|
/// </summary>
|
2024-03-20 05:47:14 +00:00
|
|
|
|
public void UpdateResultView(ICollection<ResultsForUpdate> resultsForUpdates)
|
2020-11-11 10:36:01 +00:00
|
|
|
|
{
|
2020-11-16 00:32:11 +00:00
|
|
|
|
if (!resultsForUpdates.Any())
|
|
|
|
|
|
return;
|
2020-12-04 13:53:34 +00:00
|
|
|
|
CancellationToken token;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2020-12-05 07:48:36 +00:00
|
|
|
|
// Don't know why sometimes even resultsForUpdates is empty, the method won't return;
|
|
|
|
|
|
token = resultsForUpdates.Select(r => r.Token).Distinct().SingleOrDefault();
|
2020-12-04 13:53:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Unacceptable token");
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
2020-12-04 14:30:14 +00:00
|
|
|
|
token = default;
|
2020-12-04 13:53:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2020-11-16 00:32:11 +00:00
|
|
|
|
|
2021-01-13 01:43:15 +00:00
|
|
|
|
foreach (var metaResults in resultsForUpdates)
|
2020-11-11 10:36:01 +00:00
|
|
|
|
{
|
2021-01-13 01:43:15 +00:00
|
|
|
|
foreach (var result in metaResults.Results)
|
2020-11-11 10:36:01 +00:00
|
|
|
|
{
|
2021-01-13 01:43:15 +00:00
|
|
|
|
if (_topMostRecord.IsTopMost(result))
|
|
|
|
|
|
{
|
2025-02-22 07:51:36 +00:00
|
|
|
|
result.Score = Result.MaxScore;
|
2021-01-13 01:43:15 +00:00
|
|
|
|
}
|
2025-02-20 10:06:15 +00:00
|
|
|
|
else
|
2021-01-13 01:43:15 +00:00
|
|
|
|
{
|
2021-01-17 10:05:31 +00:00
|
|
|
|
var priorityScore = metaResults.Metadata.Priority * 150;
|
2025-02-22 07:59:38 +00:00
|
|
|
|
if (result.AddSelectedCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((long)result.Score + _userSelectedRecord.GetSelectedCount(result) + priorityScore > Result.MaxScore)
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Score = Result.MaxScore;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((long)result.Score + priorityScore > Result.MaxScore)
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Score = Result.MaxScore;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
result.Score += priorityScore;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-01-13 01:43:15 +00:00
|
|
|
|
}
|
2020-11-11 10:36:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-20 05:47:14 +00:00
|
|
|
|
// it should be the same for all results
|
|
|
|
|
|
bool reSelect = resultsForUpdates.First().ReSelectFirstResult;
|
|
|
|
|
|
|
|
|
|
|
|
Results.AddResults(resultsForUpdates, token, reSelect);
|
2020-11-11 10:36:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-18 11:30:36 +00:00
|
|
|
|
#endregion
|
2016-02-12 09:20:46 +00:00
|
|
|
|
}
|
2022-08-08 04:31:38 +00:00
|
|
|
|
}
|