diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 69057820e..8ec0c6eae 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -181,6 +181,13 @@ namespace Flow.Launcher.Plugin /// The actionkeyword that is supposed to be removed void RemoveActionKeyword(string pluginId, string oldActionKeyword); + /// + /// Check whether specific ActionKeyword is assigned to any of the plugin + /// + /// The actionkeyword for checking + /// True if the actionkeyword is already assigned, False otherwise + bool ActionKeywordAssigned(string actionKeyword); + /// /// Log debug message /// Message will only be logged in Debug mode diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 81f7a2389..7cb704ccc 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -142,6 +142,8 @@ namespace Flow.Launcher public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); + public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword); + public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index ae48ac3ee..4d2a4e50a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -50,5 +50,4 @@ - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index e23bd77bd..78c822614 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -20,6 +20,7 @@ Delete Edit Add + General Setting Customise Action Keywords Quick Access Links Index Search Excluded Paths diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index d8879a4f6..5c651266a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -44,7 +44,7 @@ namespace Flow.Launcher.Plugin.Explorer if (Settings.QuickFolderAccessLinks.Any()) { Settings.QuickAccessLinks = Settings.QuickFolderAccessLinks; - Settings.QuickFolderAccessLinks = new List(); + Settings.QuickFolderAccessLinks = new(); } contextMenu = new ContextMenu(Context, Settings, viewModel); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs index 55975c2a5..b39e410d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs @@ -8,7 +8,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks { private const int quickAccessResultScore = 100; - internal static List AccessLinkListMatched(Query query, List accessLinks) + internal static List AccessLinkListMatched(Query query, IEnumerable accessLinks) { if (string.IsNullOrEmpty(query.Search)) return new List(); @@ -29,7 +29,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks }).ToList(); } - internal static List AccessLinkListAll(Query query, List accessLinks) + internal static List AccessLinkListAll(Query query, IEnumerable accessLinks) => accessLinks .OrderBy(x => x.Type) .ThenBy(x => x.Name) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 7eb9f06f3..64717b41c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -81,7 +81,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex string searchString, Func createQueryHelper, Func constructQuery, - List exclusionList, + IEnumerable exclusionList, CancellationToken token) { var regexMatch = Regex.Match(searchString, ReservedStringPattern); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 3c91eeabd..af334fdd0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -5,6 +5,7 @@ using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Text.Json.Serialization; @@ -14,14 +15,14 @@ namespace Flow.Launcher.Plugin.Explorer { public int MaxResult { get; set; } = 100; - public List QuickAccessLinks { get; set; } = new List(); + public ObservableCollection QuickAccessLinks { get; set; } = new (); // as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards. - public List QuickFolderAccessLinks { get; set; } = new List(); + public ObservableCollection QuickFolderAccessLinks { get; set; } = new (); public bool UseWindowsIndexForDirectorySearch { get; set; } = true; - public List IndexSearchExcludedSubdirectoryPaths { get; set; } = new List(); + public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection(); public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs new file mode 100644 index 000000000..2f614ead8 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ActionKeywordModel.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Flow.Launcher.Plugin.Explorer.Views +{ + public class ActionKeywordModel : INotifyPropertyChanged + { + private static Settings _settings; + + public event PropertyChangedEventHandler PropertyChanged; + + public static void Init(Settings settings) + { + _settings = settings; + } + + internal ActionKeywordModel(Settings.ActionKeyword actionKeyword, string description) + { + KeywordProperty = actionKeyword; + Description = description; + } + + public string Description { get; private init; } + + internal Settings.ActionKeyword KeywordProperty { get; } + + private void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private string? keyword; + + public string Keyword + { + get => keyword ??= _settings.GetActionKeyword(KeywordProperty); + set + { + keyword = value; + _settings.SetActionKeyword(KeywordProperty, value); + OnPropertyChanged(); + } + } + private bool? enabled; + + public bool Enabled + { + get => enabled ??= _settings.GetActionKeywordEnabled(KeywordProperty); + set + { + enabled = value; + _settings.SetActionKeywordEnabled(KeywordProperty, value); + OnPropertyChanged(); + } + } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs new file mode 100644 index 000000000..ff704b679 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/RelayCommand.cs @@ -0,0 +1,27 @@ +using System; +using System.Windows.Input; + +namespace Flow.Launcher.Plugin.Explorer.ViewModels +{ + internal class RelayCommand : ICommand + { + private Action _action; + + public RelayCommand(Action action) + { + _action = action; + } + + public virtual bool CanExecute(object parameter) + { + return true; + } + + public event EventHandler CanExecuteChanged; + + public virtual void Execute(object parameter) + { + _action?.Invoke(parameter); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index e1793ad47..182acc80a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -2,8 +2,13 @@ using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; +using Flow.Launcher.Plugin.Explorer.Views; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.IO; using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Input; namespace Flow.Launcher.Plugin.Explorer.ViewModels { @@ -25,6 +30,175 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Context.API.SaveSettingJsonStorage(); } + + public AccessLink SelectedQuickAccessLink { get; set; } + public AccessLink SelectedIndexSearchExcludedPath { get; set; } + public ActionKeywordModel SelectedActionKeyword { get; set; } + + + + public ICommand RemoveLinkCommand => new RelayCommand(RemoveLink); + public ICommand EditLinkCommand => new RelayCommand(EditLink); + public ICommand AddLinkCommand => new RelayCommand(AddLink); + + public ICommand EditActionKeywordCommand => new RelayCommand(EditActionKeyword); + + private void EditActionKeyword(object obj) + { + if (SelectedActionKeyword is not ActionKeywordModel actionKeyword) + { + ShowUnselectedMessage(); + return; + } + + var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API); + + if (actionKeywordWindow.ShowDialog() ?? false) + { + if (actionKeyword.Enabled && !actionKeywordWindow.KeywordEnabled) + { + Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + } + else if (!actionKeyword.Enabled && actionKeywordWindow.KeywordEnabled) + { + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + } + else if (actionKeyword.Enabled && actionKeywordWindow.KeywordEnabled) + { + // same keyword will have dialog result false + Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword); + } + + (actionKeyword.Keyword, actionKeyword.Enabled) = (actionKeywordWindow.ActionKeyword, actionKeywordWindow.KeywordEnabled); + } + + } + + private AccessLink? PromptUserSelectPath(ResultType type, string initialDirectory = null) + { + AccessLink newAccessLink = null; + + if (type is ResultType.Folder) + { + var folderBrowserDialog = new FolderBrowserDialog(); + + if (initialDirectory is not null) + folderBrowserDialog.InitialDirectory = initialDirectory; + + if (folderBrowserDialog.ShowDialog() != DialogResult.OK) + return newAccessLink; + + newAccessLink = new AccessLink { Path = folderBrowserDialog.SelectedPath }; + } + else if (type is ResultType.File) + { + var openFileDialog = new OpenFileDialog(); + if (initialDirectory is not null) + openFileDialog.InitialDirectory = initialDirectory; + + if (openFileDialog.ShowDialog() != DialogResult.OK) + return newAccessLink; + + newAccessLink = new AccessLink { Path = openFileDialog.FileName }; + } + return newAccessLink; + } + + private void EditLink(object obj) + { + if (obj is not string container) return; + + AccessLink selectedLink; + ObservableCollection collection; + + switch (container) + { + case "QuickAccessLink": + if (SelectedQuickAccessLink == null) + { + ShowUnselectedMessage(); + return; + } + selectedLink = SelectedQuickAccessLink; + collection = Settings.QuickAccessLinks; + break; + case "IndexSearchExcludedPaths": + if (SelectedIndexSearchExcludedPath == null) + { + ShowUnselectedMessage(); + return; + } + selectedLink = SelectedIndexSearchExcludedPath; + collection = Settings.IndexSearchExcludedSubdirectoryPaths; + break; + default: + return; + } + + var link = PromptUserSelectPath(selectedLink.Type, + selectedLink.Type == ResultType.Folder + ? selectedLink.Path + : Path.GetDirectoryName(selectedLink.Path)); + + if (link is null) + return; + + collection.Remove(selectedLink); + collection.Add(link); + } + + private void ShowUnselectedMessage() + { + string warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); + MessageBox.Show(warning); + } + + private void AddLink(object obj) + { + if (obj is not string container) return; + + var folderBrowserDialog = new FolderBrowserDialog(); + + if (folderBrowserDialog.ShowDialog() != DialogResult.OK) + return; + + var newAccessLink = new AccessLink { Path = folderBrowserDialog.SelectedPath }; + + + switch (container) + { + case "QuickAccessLink": + if (SelectedQuickAccessLink == null) return; + Settings.QuickAccessLinks.Add(newAccessLink); + break; + case "IndexSearchExcludedPaths": + if (SelectedIndexSearchExcludedPath == null) return; + Settings.IndexSearchExcludedSubdirectoryPaths.Add(newAccessLink); + break; + } + } + + private void RemoveLink(object obj) + { + if (obj is not string container) return; + + switch (container) + { + case "QuickAccessLink": + if (SelectedQuickAccessLink == null) return; + Settings.QuickAccessLinks.Remove(SelectedQuickAccessLink); + break; + case "IndexSearchExcludedPaths": + if (SelectedIndexSearchExcludedPath == null) return; + Settings.IndexSearchExcludedSubdirectoryPaths.Remove(SelectedIndexSearchExcludedPath); + break; + } + Save(); + } + + + internal void RemoveLinkFromQuickAccess(AccessLink selectedRow) => Settings.QuickAccessLinks.Remove(selectedRow); internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); @@ -41,16 +215,11 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Process.Start(psi); } - internal void UpdateActionKeyword(Settings.ActionKeyword modifiedActionKeyword, string newActionKeyword, string oldActionKeyword) - { + internal void UpdateActionKeyword(Settings.ActionKeyword modifiedActionKeyword, string newActionKeyword, string oldActionKeyword) => PluginManager.ReplaceActionKeyword(Context.CurrentPluginMetadata.ID, oldActionKeyword, newActionKeyword); - } - internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) - { - return PluginManager.ActionKeywordRegistered(newActionKeyword); - } + internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); internal bool IsNewActionKeywordGlobal(string newActionKeyword) => newActionKeyword == Query.GlobalPluginWildcardSign; } -} +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml index a46918483..e36d9b92e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml @@ -96,7 +96,7 @@ Name="ChkActionKeywordEnabled" Width="auto" VerticalAlignment="Center" - IsChecked="{Binding Enabled}" + IsChecked="{Binding KeywordEnabled}" ToolTip="{DynamicResource plugin_explorer_actionkeyword_enabled_tooltip}" /> diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs index 27e4a0b9a..918adb527 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs @@ -13,9 +13,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views /// public partial class ActionKeywordSetting : Window { - private SettingsViewModel settingsViewModel; - - public ActionKeywordView CurrentActionKeyword { get; set; } + public ActionKeywordModel CurrentActionKeyword { get; set; } public string ActionKeyword { @@ -23,24 +21,22 @@ namespace Flow.Launcher.Plugin.Explorer.Views set { // Set Enable to be true if user change ActionKeyword - Enabled = true; + KeywordEnabled = true; actionKeyword = value; } } - public bool Enabled { get; set; } + public bool KeywordEnabled { get; set; } private string actionKeyword; + private readonly IPublicAPI api; - public ActionKeywordSetting(SettingsViewModel settingsViewModel, - ActionKeywordView selectedActionKeyword) + public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api) { - this.settingsViewModel = settingsViewModel; - CurrentActionKeyword = selectedActionKeyword; - + this.api = api; ActionKeyword = selectedActionKeyword.Keyword; - Enabled = selectedActionKeyword.Enabled; + KeywordEnabled = selectedActionKeyword.Enabled; InitializeComponent(); @@ -52,56 +48,43 @@ namespace Flow.Launcher.Plugin.Explorer.Views if (string.IsNullOrEmpty(ActionKeyword)) ActionKeyword = Query.GlobalPluginWildcardSign; - if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == Enabled) + if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == KeywordEnabled) { + DialogResult = false; Close(); return; } + if (ActionKeyword == "") + { + ActionKeyword = "*"; + } if (ActionKeyword == Query.GlobalPluginWildcardSign) switch (CurrentActionKeyword.KeywordProperty) { case Settings.ActionKeyword.FileContentSearchActionKeyword: - MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); + MessageBox.Show(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); return; case Settings.ActionKeyword.QuickAccessActionKeyword: - MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); + MessageBox.Show(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); return; } - var oldActionKeyword = CurrentActionKeyword.Keyword; - - if (!Enabled || !settingsViewModel.IsActionKeywordAlreadyAssigned(ActionKeyword)) + if (!KeywordEnabled || !api.ActionKeywordAssigned(ActionKeyword)) { - // Update View Data - CurrentActionKeyword.Keyword = Enabled == true ? ActionKeyword : Query.GlobalPluginWildcardSign; - CurrentActionKeyword.Enabled = Enabled; - - switch (Enabled) - { - // reset to global so it does not take up an action keyword when disabled - // not for null Enable plugin - case false when oldActionKeyword != Query.GlobalPluginWildcardSign: - settingsViewModel.UpdateActionKeyword(CurrentActionKeyword.KeywordProperty, - Query.GlobalPluginWildcardSign, oldActionKeyword); - break; - default: - settingsViewModel.UpdateActionKeyword(CurrentActionKeyword.KeywordProperty, - CurrentActionKeyword.Keyword, oldActionKeyword); - break; - } - + DialogResult = true; Close(); return; } // The keyword is not valid, so show message - MessageBox.Show(settingsViewModel.Context.API.GetTranslation("newActionKeywordsHasBeenAssigned")); + MessageBox.Show(api.GetTranslation("newActionKeywordsHasBeenAssigned")); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) { + DialogResult = false; Close(); } private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index a441c10b5..1e14ba09d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -4,13 +4,13 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels" xmlns:views="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" - xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" + d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}" d:DesignHeight="450" d:DesignWidth="800" - mc:Ignorable="d" - d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}"> + mc:Ignorable="d"> @@ -37,7 +37,7 @@ + Text="{Binding Keyword}">