diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs index f98815c1a..bb1279b2c 100644 --- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs +++ b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs @@ -1,4 +1,6 @@ -namespace Flow.Launcher.Core.ExternalPlugins +using System; + +namespace Flow.Launcher.Core.ExternalPlugins { public record UserPlugin { @@ -12,5 +14,8 @@ public string UrlDownload { get; set; } public string UrlSourceCode { get; set; } public string IcoPath { get; set; } + public DateTime LatestReleaseDate { get; set; } + public DateTime DateAdded { get; set; } + } } diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index f06bf58e6..0400a6879 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -21,6 +21,7 @@ namespace Flow.Launcher.Infrastructure public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins); public const string Issue = "https://github.com/Flow-Launcher/Flow.Launcher/issues/new"; public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion; + public static readonly string Dev = "Dev"; public const string Documentation = "https://flowlauncher.com/docs/#/usage-tips"; public static readonly int ThumbnailSize = 64; diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 4a7bc20e3..566e96502 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -58,7 +58,7 @@ - + diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 75f208c9e..b8f1408e7 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using NLog; diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 3ffa9f7b1..46165a849 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -202,7 +202,11 @@ namespace Flow.Launcher.Infrastructure if (allQuerySubstringsMatched) { var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex); - var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, + + // firstMatchIndex - nearestSpaceIndex - 1 is to set the firstIndex as the index of the first matched char + // preceded by a space e.g. 'world' matching 'hello world' firstIndex would be 0 not 6 + // giving more weight than 'we or donald' by allowing the distance calculation to treat the starting position at after the space. + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, spaceIndices, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); var resultList = indexList.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); @@ -296,7 +300,7 @@ namespace Flow.Launcher.Infrastructure return currentQuerySubstringIndex >= querySubstringsLength; } - private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, + private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, List spaceIndices, int matchLen, bool allSubstringsContainedInCompareString) { // A match found near the beginning of a string is scored more than a match found near the end @@ -304,6 +308,14 @@ namespace Flow.Launcher.Infrastructure // while the score is lower if they are more spread out var score = 100 * (query.Length + 1) / ((1 + firstIndex) + (matchLen + 1)); + // Give more weight to a match that is closer to the start of the string. + // if the first matched char is immediately before space and all strings are contained in the compare string e.g. 'world' matching 'hello world' + // and 'world hello', because both have 'world' immediately preceded by space, their firstIndex will be 0 when distance is calculated, + // to prevent them scoring the same, we adjust the score by deducting the number of spaces it has from the start of the string, so 'world hello' + // will score slightly higher than 'hello world' because 'hello world' has one additional space. + if (firstIndex == 0 && allSubstringsContainedInCompareString) + score -= spaceIndices.Count; + // A match with less characters assigning more weights if (stringToCompare.Length - query.Length < 5) { diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs new file mode 100644 index 000000000..71020369a --- /dev/null +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Text.Json.Serialization; + +namespace Flow.Launcher.Infrastructure.UserSettings +{ + public abstract class ShortcutBaseModel + { + public string Key { get; set; } + + [JsonIgnore] + public Func Expand { get; set; } = () => { return ""; }; + + public override bool Equals(object obj) + { + return obj is ShortcutBaseModel other && + Key == other.Key; + } + + public override int GetHashCode() + { + return Key.GetHashCode(); + } + } + + public class CustomShortcutModel : ShortcutBaseModel + { + public string Value { get; set; } + + [JsonConstructorAttribute] + public CustomShortcutModel(string key, string value) + { + Key = key; + Value = value; + Expand = () => { return Value; }; + } + + public void Deconstruct(out string key, out string value) + { + key = Key; + value = Value; + } + + public static implicit operator (string Key, string Value)(CustomShortcutModel shortcut) + { + return (shortcut.Key, shortcut.Value); + } + + public static implicit operator CustomShortcutModel((string Key, string Value) shortcut) + { + return new CustomShortcutModel(shortcut.Key, shortcut.Value); + } + } + + public class BuiltinShortcutModel : ShortcutBaseModel + { + public string Description { get; set; } + + public BuiltinShortcutModel(string key, string description, Func expand) + { + Key = key; + Description = description; + Expand = expand ?? (() => { return ""; }); + } + } +} diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index d42fd425f..33072b53d 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.Text.Json.Serialization; +using System.Windows; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher; @@ -41,12 +42,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings public bool UseGlyphIcons { get; set; } = true; public bool UseAnimation { get; set; } = true; public bool UseSound { get; set; } = true; + public bool UseClock { get; set; } = true; + public bool UseDate { get; set; } = false; + public string TimeFormat { get; set; } = "hh:mm tt"; + public string DateFormat { get; set; } = "MM'/'dd ddd"; public bool FirstLaunch { get; set; } = true; public double SettingWindowWidth { get; set; } = 1000; public double SettingWindowHeight { get; set; } = 700; public double SettingWindowTop { get; set; } public double SettingWindowLeft { get; set; } + public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; public int CustomExplorerIndex { get; set; } = 0; @@ -125,8 +131,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings PrivateArg = "-private", EnablePrivate = false, Editable = false - } - , + }, new() { Name = "MS Edge", @@ -182,6 +187,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings public ObservableCollection CustomPluginHotkeys { get; set; } = new ObservableCollection(); + public ObservableCollection CustomShortcuts { get; set; } = new ObservableCollection(); + + [JsonIgnore] + public ObservableCollection BuiltinShortcuts { get; set; } = new ObservableCollection() { + new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText) + }; + public bool DontPromptUpdateMsg { get; set; } public bool EnableUpdateLog { get; set; } diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 7633e34a7..a1d3b83ab 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -37,7 +37,11 @@ namespace Flow.Launcher.Plugin /// user's clipboard when Ctrl + C is pressed on a result. If the text is a file/directory path /// flow will copy the actual file/folder instead of just the path text. /// - public string CopyText { get; set; } = string.Empty; + public string CopyText + { + get => string.IsNullOrEmpty(_copyText) ? SubTitle : _copyText; + set => _copyText = value; + } /// /// This holds the text which can be provided by plugin to help Flow autocomplete text @@ -81,6 +85,7 @@ namespace Flow.Launcher.Plugin /// Delegate to Get Image Source /// public IconDelegate Icon; + private string _copyText = string.Empty; /// /// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index bbddcbd2a..46c848c7a 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -129,14 +129,20 @@ namespace Flow.Launcher.Test } } + + /// + /// These are standard match scenarios + /// The intention of this test is provide a bench mark for how much the score has increased from a change. + /// Usually the increase in scoring should not be drastic, increase of less than 10 is acceptable. + /// [TestCase(Chrome, Chrome, 157)] - [TestCase(Chrome, LastIsChrome, 147)] + [TestCase(Chrome, LastIsChrome, 145)] [TestCase("chro", HelpCureHopeRaiseOnMindEntityChrome, 50)] [TestCase("chr", HelpCureHopeRaiseOnMindEntityChrome, 30)] [TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)] [TestCase(Chrome, CandyCrushSagaFromKing, 0)] - [TestCase("sql", MicrosoftSqlServerManagementStudio, 110)] - [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended + [TestCase("sql", MicrosoftSqlServerManagementStudio, 109)] + [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 120)] //double spacing intended public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( string queryString, string compareString, int expectedScore) { @@ -275,7 +281,40 @@ namespace Flow.Launcher.Test $"Query: \"{queryString}\"{Environment.NewLine} " + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" + $"Should be greater than{Environment.NewLine}" + - $"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}"); + $"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}"); + } + + [TestCase("red", "red colour", "metro red")] + [TestCase("red", "this red colour", "this colour red")] + [TestCase("red", "this red colour", "this colour is very red")] + [TestCase("red", "this red colour", "this colour is surprisingly super awesome red and cool")] + [TestCase("red", "this colour is surprisingly super red very and cool", "this colour is surprisingly super very red and cool")] + public void WhenGivenTwoStrings_Scoring_ShouldGiveMoreWeightToTheStringCloserToIndexZero( + string queryString, string compareString1, string compareString2) + { + // When + var matcher = new StringMatcher { UserSettingSearchPrecision = SearchPrecisionScore.Regular }; + + // Given + var compareString1Result = matcher.FuzzyMatch(queryString, compareString1); + var compareString2Result = matcher.FuzzyMatch(queryString, compareString2); + + Debug.WriteLine(""); + Debug.WriteLine("###############################################"); + Debug.WriteLine($"QueryString: \"{queryString}\"{Environment.NewLine}"); + Debug.WriteLine( + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}"); + Debug.WriteLine( + $"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}"); + Debug.WriteLine("###############################################"); + Debug.WriteLine(""); + + // Should + Assert.True(compareString1Result.Score > compareString2Result.Score, + $"Query: \"{queryString}\"{Environment.NewLine} " + + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" + + $"Should be greater than{Environment.NewLine}" + + $"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}"); } [TestCase("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")] diff --git a/Flow.Launcher.Test/Plugins/ProgramTest.cs b/Flow.Launcher.Test/Plugins/ProgramTest.cs index a0d2243ce..e3a05f484 100644 --- a/Flow.Launcher.Test/Plugins/ProgramTest.cs +++ b/Flow.Launcher.Test/Plugins/ProgramTest.cs @@ -19,7 +19,7 @@ namespace Flow.Launcher.Test.Plugins var app = new UWP.Application(); // Act - var result = app.FormattedPriReferenceValue(packageName, rawPriReferenceValue); + var result = UWP.Application.FormattedPriReferenceValue(packageName, rawPriReferenceValue); // Assert Assert.IsTrue(result == expectedFormat, diff --git a/Flow.Launcher/Converters/BoolToVisibilityConverter.cs b/Flow.Launcher/Converters/BoolToVisibilityConverter.cs new file mode 100644 index 000000000..ad474d693 --- /dev/null +++ b/Flow.Launcher/Converters/BoolToVisibilityConverter.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; + +namespace Flow.Launcher.Converters +{ + public class BoolToVisibilityConverter : IValueConverter + { + public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) + { + if (parameter != null) + { + if (value is true) + { + return Visibility.Collapsed; + } + + else + { + return Visibility.Visible; + } + } + else { + if (value is true) + { + return Visibility.Visible; + } + + else { + return Visibility.Collapsed; + } + } + } + + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + } +} diff --git a/Flow.Launcher/Converters/TextConverter.cs b/Flow.Launcher/Converters/TextConverter.cs new file mode 100644 index 000000000..90d445776 --- /dev/null +++ b/Flow.Launcher/Converters/TextConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using Flow.Launcher.Core.Resource; +using Flow.Launcher.ViewModel; + +namespace Flow.Launcher.Converters +{ + public class TextConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var ID = value.ToString(); + switch(ID) + { + case PluginStoreItemViewModel.NewRelease: + return InternationalizationManager.Instance.GetTranslation("pluginStore_NewRelease"); + case PluginStoreItemViewModel.RecentlyUpdated: + return InternationalizationManager.Instance.GetTranslation("pluginStore_RecentlyUpdated"); + case PluginStoreItemViewModel.None: + return InternationalizationManager.Instance.GetTranslation("pluginStore_None"); + case PluginStoreItemViewModel.Installed: + return InternationalizationManager.Instance.GetTranslation("pluginStore_Installed"); + default: + return ID; + } + + } + + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + } +} diff --git a/Flow.Launcher/Converters/TranslationConverter.cs b/Flow.Launcher/Converters/TranslationConverter.cs new file mode 100644 index 000000000..e1e8a58e3 --- /dev/null +++ b/Flow.Launcher/Converters/TranslationConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using Flow.Launcher.Core.Resource; + +namespace Flow.Launcher.Converters +{ + public class TranlationConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var key = value.ToString(); + if (String.IsNullOrEmpty(key)) + return key; + return InternationalizationManager.Instance.GetTranslation(key); + } + + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + } +} diff --git a/Flow.Launcher/CustomShortcutSetting.xaml b/Flow.Launcher/CustomShortcutSetting.xaml new file mode 100644 index 000000000..78b392f3e --- /dev/null +++ b/Flow.Launcher/CustomShortcutSetting.xaml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/CustomShortcutSetting.xaml.cs b/Flow.Launcher/CustomShortcutSetting.xaml.cs new file mode 100644 index 000000000..097d6a53b --- /dev/null +++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs @@ -0,0 +1,73 @@ +using Flow.Launcher.Core.Resource; +using Flow.Launcher.ViewModel; +using System; +using System.Windows; +using System.Windows.Input; + +namespace Flow.Launcher +{ + public partial class CustomShortcutSetting : Window + { + private SettingWindowViewModel viewModel; + public string Key { get; set; } = String.Empty; + public string Value { get; set; } = String.Empty; + private string originalKey { get; init; } = null; + private string originalValue { get; init; } = null; + private bool update { get; init; } = false; + + public CustomShortcutSetting(SettingWindowViewModel vm) + { + viewModel = vm; + InitializeComponent(); + } + + public CustomShortcutSetting(string key, string value, SettingWindowViewModel vm) + { + viewModel = vm; + Key = key; + Value = value; + originalKey = key; + originalValue = value; + update = true; + InitializeComponent(); + } + + private void BtnCancel_OnClick(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } + + private void BtnAdd_OnClick(object sender, RoutedEventArgs e) + { + if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value)) + { + MessageBox.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut")); + return; + } + // Check if key is modified or adding a new one + if (((update && originalKey != Key) || !update) + && viewModel.ShortcutExists(Key)) + { + MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut")); + return; + } + DialogResult = !update || originalKey != Key || originalValue != Value; + Close(); + } + + private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e) + { + DialogResult = false; + Close(); + } + + private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e) + { + App.API.ChangeQuery(tbExpand.Text); + Application.Current.MainWindow.Show(); + Application.Current.MainWindow.Opacity = 1; + Application.Current.MainWindow.Focus(); + } + } +} diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 813a44527..c1fe421fb 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -89,7 +89,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -98,6 +98,7 @@ + @@ -115,12 +116,12 @@ - + - + diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index a3ad20f77..b9ac6afb3 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -17,7 +17,7 @@ namespace Flow.Launcher.Helper internal static void Initialize(MainViewModel mainVM) { mainViewModel = mainVM; - settings = mainViewModel._settings; + settings = mainViewModel.Settings; SetHotkey(settings.Hotkey, OnToggleHotkey); LoadCustomPluginHotkey(); diff --git a/Flow.Launcher/HotkeyControl.xaml b/Flow.Launcher/HotkeyControl.xaml index 9b5f671d8..5a593d20a 100644 --- a/Flow.Launcher/HotkeyControl.xaml +++ b/Flow.Launcher/HotkeyControl.xaml @@ -38,9 +38,8 @@ FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource Color05B}" - Visibility="Visible"> - Press key - + Text="{DynamicResource flowlauncherPressHotkey}" + Visibility="Visible" /> @@ -49,8 +48,8 @@ Margin="0,0,18,0" VerticalContentAlignment="Center" input:InputMethod.IsInputMethodEnabled="False" + LostFocus="tbHotkey_LostFocus" PreviewKeyDown="TbHotkey_OnPreviewKeyDown" - TabIndex="100" - LostFocus="tbHotkey_LostFocus"/> + TabIndex="100" /> \ No newline at end of file diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 7e07695d8..d746c8fd2 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -9,6 +9,7 @@ using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin; using System.Threading; +using System.Windows.Interop; namespace Flow.Launcher { @@ -113,7 +114,7 @@ namespace Flow.Launcher private void tbHotkey_LostFocus(object sender, RoutedEventArgs e) { tbMsg.Text = tbMsgTextOriginal; - tbMsg.Foreground = tbMsgForegroundColorOriginal; + tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B"); } } } diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml index f430bd8a3..72211f877 100644 --- a/Flow.Launcher/Languages/da.xaml +++ b/Flow.Launcher/Languages/da.xaml @@ -76,13 +76,13 @@ Søgetid: | Version Website - Uninstall + Uninstall Plugin Store Refresh - Install + Install Tema diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml index 09881009b..eb71a0a82 100644 --- a/Flow.Launcher/Languages/de.xaml +++ b/Flow.Launcher/Languages/de.xaml @@ -76,13 +76,13 @@ Abfragezeit: Version Webseite - Deinstallieren + Deinstallieren Erweiterungen laden Aktualisieren - Installieren + Installieren Design diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 92f2dbded..75278cb91 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -27,7 +27,7 @@ Reset search window position - Flow Launcher Settings + Settings General Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). @@ -100,8 +100,20 @@ Plugin Store + New Release + Recently Updated + Plugins + Installed Refresh - Install + Install + Uninstall + Update + Plugin already installed + New Version + This plugin has been updated within the last 7 days + New Update is Available + + Theme @@ -124,6 +136,8 @@ Play a small sound when the search window opens Animation Use Animation in UI + Clock + Date Hotkey @@ -134,18 +148,26 @@ Show Hotkey Show result selection hotkey with results. Custom Query Hotkey + Custom Query Shortcut + Built-in Shortcuts Query + Shortcut + Expanded + Description Delete Edit Add Please select an item Are you sure you want to delete {0} plugin hotkey? + Are you sure you want to delete shortcut: {0} with expansion {1}? + Get text from clipboard. Query window shadow effect Shadow effect has a substantial usage of GPU. Not recommended if your computer performance is limited. Window Width Size You can also quickly adjust this by using Ctrl+[ and Ctrl+]. Use Segoe Fluent Icons Use Segoe Fluent Icons for query results where supported + Press Key HTTP Proxy @@ -169,6 +191,7 @@ Github Docs Version + Icons You have activated Flow Launcher {0} times Check for Updates New version {0} is available, would you like to restart Flow Launcher to use the update? @@ -231,6 +254,12 @@ Invalid plugin hotkey Update + + Custom Query Shortcut + Enter a shortcut that automatically expands to the specified query. + Shortcut already exists, please enter a new Shortcut or edit the existing one. + Shortcut and/or its expansion is empty. + Hotkey Unavailable diff --git a/Flow.Launcher/Languages/es-419.xaml b/Flow.Launcher/Languages/es-419.xaml index 7afcb711e..ec81c8169 100644 --- a/Flow.Launcher/Languages/es-419.xaml +++ b/Flow.Launcher/Languages/es-419.xaml @@ -76,13 +76,13 @@ Tiempo de consulta: | Versión Sitio web - Uninstall + Uninstall Tienda de Plugins Recargar - Instalar + Instalar Tema diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml index 189417170..a11050fa8 100644 --- a/Flow.Launcher/Languages/fr.xaml +++ b/Flow.Launcher/Languages/fr.xaml @@ -76,13 +76,13 @@ Utilisation : | Version Website - Désinstaller + Désinstaller Plugin Store Refresh - Install + Install Thèmes diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml index 59298c048..3e0c24ddb 100644 --- a/Flow.Launcher/Languages/it.xaml +++ b/Flow.Launcher/Languages/it.xaml @@ -76,13 +76,13 @@ Tempo ricerca: | Versione Sito Web - Disinstalla + Disinstalla Negozio dei Plugin Aggiorna - Installa + Installa Tema diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml index 27f2210c6..f71b5834c 100644 --- a/Flow.Launcher/Languages/ja.xaml +++ b/Flow.Launcher/Languages/ja.xaml @@ -76,13 +76,13 @@ クエリ時間: | バージョン ウェブサイト - アンインストール + アンインストール プラグインストア Refresh - Install + Install テーマ diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 4f6cf98c0..c9d4b8b69 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -76,13 +76,13 @@ 쿼리 시간: | 버전 웹사이트 - 제거 + 제거 플러그인 스토어 새로고침 - 설치 + 설치 테마 diff --git a/Flow.Launcher/Languages/nb.xaml b/Flow.Launcher/Languages/nb.xaml index e94ef8e20..daca59eb2 100644 --- a/Flow.Launcher/Languages/nb.xaml +++ b/Flow.Launcher/Languages/nb.xaml @@ -76,13 +76,13 @@ Query time: | Version Website - Uninstall + Uninstall Plugin Store Refresh - Install + Install Theme diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml index 6b0737a61..db6600582 100644 --- a/Flow.Launcher/Languages/nl.xaml +++ b/Flow.Launcher/Languages/nl.xaml @@ -76,13 +76,13 @@ Query tijd: | Versie Website - Uninstall + Uninstall Plugin Winkel Vernieuwen - Installeren + Installeren Thema diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml index d461f0552..19a830813 100644 --- a/Flow.Launcher/Languages/pl.xaml +++ b/Flow.Launcher/Languages/pl.xaml @@ -76,13 +76,13 @@ Czas zapytania: | Version Website - Odinstalowywanie + Odinstalowywanie Plugin Store Refresh - Install + Install Skórka diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml index 1037f79d6..80b2aa825 100644 --- a/Flow.Launcher/Languages/pt-br.xaml +++ b/Flow.Launcher/Languages/pt-br.xaml @@ -76,13 +76,13 @@ Tempo de consulta: | Version Website - Desinstalar + Desinstalar Plugin Store Refresh - Install + Install Tema diff --git a/Flow.Launcher/Languages/pt-pt.xaml b/Flow.Launcher/Languages/pt-pt.xaml index 2ae8f3baa..9d4d12f2c 100644 --- a/Flow.Launcher/Languages/pt-pt.xaml +++ b/Flow.Launcher/Languages/pt-pt.xaml @@ -76,13 +76,13 @@ Tempo de consulta: | Versão Site - Desinstalar + Desinstalar Loja de plugins Recarregar - Instalar + Instalar Tema diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml index a62b0544b..f4a2cf73f 100644 --- a/Flow.Launcher/Languages/ru.xaml +++ b/Flow.Launcher/Languages/ru.xaml @@ -76,13 +76,13 @@ Запрос: | Version Website - Удалить + Удалить Plugin Store Refresh - Install + Install Тема diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index fc2ae710e..28759650b 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -76,13 +76,13 @@ Trvanie dopytu: | Verzia Webstránka - Odinštalovať + Odinštalovať Repozitár pluginov Obnoviť - Inštalovať + Inštalovať Motív diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml index 82c40b8fa..aed21e7b6 100644 --- a/Flow.Launcher/Languages/sr.xaml +++ b/Flow.Launcher/Languages/sr.xaml @@ -76,13 +76,13 @@ Vreme upita: | Version Website - Uninstall + Uninstall Plugin Store Refresh - Install + Install Tema diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml index e888612ce..c89d38915 100644 --- a/Flow.Launcher/Languages/tr.xaml +++ b/Flow.Launcher/Languages/tr.xaml @@ -76,13 +76,13 @@ Sorgu Süresi: Sürüm İnternet Sitesi - Kaldır + Kaldır Eklenti Mağazası Yenile - İndir + İndir Temalar diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml index 95a9311b4..43874b5ed 100644 --- a/Flow.Launcher/Languages/uk-UA.xaml +++ b/Flow.Launcher/Languages/uk-UA.xaml @@ -76,13 +76,13 @@ Запит: | Версія Сайт - Uninstall + Uninstall Магазин плагінів Оновити - Встановити + Встановити Тема diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml index a495e294e..d8b2e8d69 100644 --- a/Flow.Launcher/Languages/zh-cn.xaml +++ b/Flow.Launcher/Languages/zh-cn.xaml @@ -76,13 +76,13 @@ 查询耗时: | 版本 官方网站 - 卸载 + 卸载 插件商店 刷新 - 安装 + 安装 主题 diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml index 17990b80d..52440e396 100644 --- a/Flow.Launcher/Languages/zh-tw.xaml +++ b/Flow.Launcher/Languages/zh-tw.xaml @@ -76,13 +76,13 @@ 查詢耗時: | 版本 官方網站 - 解除安裝 + 解除安裝 外掛商店 重新整理 - 安裝 + 安裝 主題 diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 9f7d16ab1..17444aae8 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -1,5 +1,4 @@ - + - - - - - + + + + - - - - - - - - - - - - - - - - + + - + + + + + + + - - - + - - - - - - - - - - - - + - - - - + + + - - + - - - - + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + - - - - - - - + + + - + LeftClickResultCommand="{Binding LeftClickResultCommand}" + RightClickResultCommand="{Binding RightClickResultCommand}" /> - - - + + + - + LeftClickResultCommand="{Binding LeftClickResultCommand}" + RightClickResultCommand="{Binding RightClickResultCommand}" /> - - - + + + - + LeftClickResultCommand="{Binding LeftClickResultCommand}" + RightClickResultCommand="{Binding RightClickResultCommand}" /> - + \ No newline at end of file diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index fe6c119e1..10b22e33a 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -20,8 +20,16 @@ using Flow.Launcher.Infrastructure; using System.Windows.Media; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin.SharedCommands; -using System.Windows.Data; +using System.Text; +using DataObject = System.Windows.DataObject; using System.Diagnostics; +using Microsoft.AspNetCore.Http; +using System.IO; +using System.Windows.Threading; +using System.Windows.Data; +using ModernWpf.Controls; +using System.Drawing; +using System.Windows.Forms.Design.Behavior; namespace Flow.Launcher { @@ -45,6 +53,7 @@ namespace Flow.Launcher DataContext = mainVM; _viewModel = mainVM; _settings = settings; + InitializeComponent(); InitializePosition(); animationSound.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav")); @@ -54,6 +63,7 @@ namespace Flow.Launcher { InitializeComponent(); } + private void OnCopy(object sender, ExecutedRoutedEventArgs e) { if (QueryTextBox.SelectionLength == 0) @@ -217,11 +227,12 @@ namespace Flow.Launcher private void UpdateNotifyIconText() { var menu = contextMenu; - ((MenuItem)menu.Items[1]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")"; - ((MenuItem)menu.Items[2]).Header = InternationalizationManager.Instance.GetTranslation("GameMode"); - ((MenuItem)menu.Items[3]).Header = InternationalizationManager.Instance.GetTranslation("PositionReset"); - ((MenuItem)menu.Items[4]).Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"); - ((MenuItem)menu.Items[5]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"); + ((MenuItem)menu.Items[0]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")"; + ((MenuItem)menu.Items[1]).Header = InternationalizationManager.Instance.GetTranslation("GameMode"); + ((MenuItem)menu.Items[2]).Header = InternationalizationManager.Instance.GetTranslation("PositionReset"); + ((MenuItem)menu.Items[3]).Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"); + ((MenuItem)menu.Items[4]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"); + } private void InitializeNotifyIcon() @@ -233,31 +244,35 @@ namespace Flow.Launcher Visible = !_settings.HideNotifyIcon }; contextMenu = new ContextMenu(); - - var header = new MenuItem - { - Header = "Flow Launcher", - IsEnabled = false - }; + var openIcon = new FontIcon { Glyph = "\ue71e" }; var open = new MenuItem { - Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" +_settings.Hotkey + ")" + Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", + Icon = openIcon }; + var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; var gamemode = new MenuItem { - Header = InternationalizationManager.Instance.GetTranslation("GameMode") + Header = InternationalizationManager.Instance.GetTranslation("GameMode"), + Icon = gamemodeIcon }; + var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; var positionreset = new MenuItem { - Header = InternationalizationManager.Instance.GetTranslation("PositionReset") + Header = InternationalizationManager.Instance.GetTranslation("PositionReset"), + Icon = positionresetIcon }; + var settingsIcon = new FontIcon { Glyph = "\ue713" }; var settings = new MenuItem { - Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings") + Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"), + Icon = settingsIcon }; + var exitIcon = new FontIcon { Glyph = "\ue7e8" }; var exit = new MenuItem { - Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit") + Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"), + Icon = exitIcon }; open.Click += (o, e) => _viewModel.ToggleFlowLauncher(); @@ -265,7 +280,6 @@ namespace Flow.Launcher positionreset.Click += (o, e) => PositionReset(); settings.Click += (o, e) => App.API.OpenSettingDialog(); exit.Click += (o, e) => Close(); - contextMenu.Items.Add(header); contextMenu.Items.Add(open); gamemode.ToolTip = InternationalizationManager.Instance.GetTranslation("GameModeToolTip"); positionreset.ToolTip = InternationalizationManager.Instance.GetTranslation("PositionResetToolTip"); @@ -391,28 +405,6 @@ namespace Flow.Launcher if (e.ChangedButton == MouseButton.Left) DragMove(); } - private void OnPreviewMouseButtonDown(object sender, MouseButtonEventArgs e) - { - if (sender != null && e.OriginalSource != null) - { - var r = (ResultListBox)sender; - var d = (DependencyObject)e.OriginalSource; - var item = ItemsControl.ContainerFromElement(r, d) as ListBoxItem; - var result = (ResultViewModel)item?.DataContext; - if (result != null) - { - if (e.ChangedButton == MouseButton.Left) - { - _viewModel.OpenResultCommand.Execute(null); - } - else if (e.ChangedButton == MouseButton.Right) - { - _viewModel.LoadContextMenuCommand.Execute(null); - } - } - } - } - private void OnPreviewDragOver(object sender, DragEventArgs e) { e.Handled = true; diff --git a/Flow.Launcher/Resources/CustomControlTemplate.xaml b/Flow.Launcher/Resources/CustomControlTemplate.xaml index 6ab7ab5ad..07897361c 100644 --- a/Flow.Launcher/Resources/CustomControlTemplate.xaml +++ b/Flow.Launcher/Resources/CustomControlTemplate.xaml @@ -1407,7 +1407,8 @@ Padding="{TemplateBinding Padding}" BorderBrush="{DynamicResource ButtonInsideBorder}" BorderThickness="{DynamicResource CustomButtonInsideBorderThickness}" - CornerRadius="4"> + CornerRadius="4" + SnapsToDevicePixels="True"> - + - - + + + + + + + @@ -2077,12 +2090,12 @@ - + - + @@ -2110,7 +2123,7 @@ x:Name="HeaderSite" MinWidth="0" MinHeight="0" - Margin="18,0,0,0" + Margin="18,0,18,0" Padding="{TemplateBinding Padding}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" @@ -2127,19 +2140,62 @@ Foreground="{TemplateBinding Foreground}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpanderDownHeaderStyle}" /> - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2672,73 +2728,491 @@ - + + - + + + + + diff --git a/Flow.Launcher/Resources/Dark.xaml b/Flow.Launcher/Resources/Dark.xaml index 445e07c63..a5eec29ab 100644 --- a/Flow.Launcher/Resources/Dark.xaml +++ b/Flow.Launcher/Resources/Dark.xaml @@ -65,10 +65,13 @@ - + + + #272727 + #202020 #2b2b2b #1d1d1d diff --git a/Flow.Launcher/Resources/Light.xaml b/Flow.Launcher/Resources/Light.xaml index cee8b63cd..a78b14d65 100644 --- a/Flow.Launcher/Resources/Light.xaml +++ b/Flow.Launcher/Resources/Light.xaml @@ -58,10 +58,13 @@ - + + + #f6f6f6 + #f3f3f3 #ffffff #e5e5e5 diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 5c497b925..691158630 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -25,12 +25,17 @@ VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard" Visibility="{Binding Visbility}" - mc:Ignorable="d"> + mc:Ignorable="d" + PreviewMouseMove="ResultList_MouseMove" + PreviewMouseLeftButtonDown="ResultList_PreviewMouseLeftButtonDown" + PreviewMouseLeftButtonUp="ResultListBox_OnPreviewMouseUp" + PreviewMouseRightButtonDown="ResultListBox_OnPreviewMouseRightButtonDown"> - + + + + - + @@ -1751,7 +1788,9 @@ + ScrollViewer.CanContentScroll="True" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.ScrollUnit="Pixel"> @@ -1798,8 +1837,11 @@ IsReadOnly="True" Style="{DynamicResource QueryBoxStyle}" Text="{DynamicResource hiThere}" /> - + + + + + + + + + + + + + + + +  + + + + + + + + + + + + + + +  + + + + + + + @@ -2224,7 +2347,7 @@ - + @@ -2243,17 +2366,11 @@ + ScrollViewer.CanContentScroll="True" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.ScrollUnit="Pixel"> - - - - - - - - - + - + @@ -2325,7 +2442,7 @@ Style="{StaticResource SettingSeparatorStyle}" /> @@ -2337,11 +2454,10 @@ Text="{DynamicResource showOpenResultHotkey}" /> - + IsOn="{Binding Settings.ShowOpenResultHotkey}" + Style="{DynamicResource SideToggleSwitch}" /> @@ -2349,7 +2465,7 @@ - + @@ -2397,7 +2514,7 @@ -  +  + - diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index ced7009a3..37c6fa338 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -1,31 +1,28 @@ -using Droplex; -using Flow.Launcher.Core.ExternalPlugins; -using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.ViewModel; -using Microsoft.Win32; using ModernWpf; +using ModernWpf.Controls; using System; +using System.Drawing.Printing; using System.IO; -using System.Linq; using System.Windows; -using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Navigation; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; using Button = System.Windows.Controls.Button; using Control = System.Windows.Controls.Control; -using ListViewItem = System.Windows.Controls.ListViewItem; using KeyEventArgs = System.Windows.Input.KeyEventArgs; using MessageBox = System.Windows.MessageBox; using TextBox = System.Windows.Controls.TextBox; @@ -68,6 +65,7 @@ namespace Flow.Launcher pluginStoreView.Filter = PluginStoreFilter; InitializePosition(); + ClockDisplay(); } private void OnSelectPythonFilePathClick(object sender, RoutedEventArgs e) @@ -150,7 +148,7 @@ namespace Flow.Launcher } } - private void OnnEditCustomHotkeyClick(object sender, RoutedEventArgs e) + private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e) { var item = viewModel.SelectedCustomPluginHotkey; if (item != null) @@ -248,6 +246,7 @@ namespace Flow.Launcher private void OnClosed(object sender, EventArgs e) { + settings.SettingWindowState = WindowState; settings.SettingWindowTop = Top; settings.SettingWindowLeft = Left; viewModel.Save(); @@ -292,20 +291,69 @@ namespace Flow.Launcher } } - private void OnPluginStoreRefreshClick(object sender, RoutedEventArgs e) + private static T FindParent(DependencyObject child) where T : DependencyObject { - _ = viewModel.RefreshExternalPluginsAsync(); - } + //get parent item + DependencyObject parentObject = VisualTreeHelper.GetParent(child); + //we've reached the end of the tree + if (parentObject == null) return null; + + //check if the parent matches the type we're looking for + T parent = parentObject as T; + if (parent != null) + return parent; + else + return FindParent(parentObject); + } + private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e) { - if (sender is Button { DataContext: UserPlugin plugin }) + if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button) { - var pluginsManagerPlugin = PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"); - var actionKeyword = pluginsManagerPlugin.Metadata.ActionKeywords.Count == 0 ? "" : pluginsManagerPlugin.Metadata.ActionKeywords[0]; - API.ChangeQuery($"{actionKeyword} install {plugin.Name}"); - API.ShowMainWindow(); + return; } + + if (storeClickedButton != null) + { + FlyoutService.GetFlyout(storeClickedButton).Hide(); + } + + viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + } + + private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left) + { + var name = viewModel.SelectedPlugin.PluginPair.Metadata.Name; + viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + } + + + } + + private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e) + { + if (storeClickedButton != null) + { + FlyoutService.GetFlyout(storeClickedButton).Hide(); + } + + if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) + viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + + } + + private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e) + { + if (storeClickedButton != null) + { + FlyoutService.GetFlyout(storeClickedButton).Hide(); + } + if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) + viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + } private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ @@ -358,11 +406,34 @@ namespace Flow.Launcher restoreButton.Visibility = Visibility.Collapsed; } } + private void Window_StateChanged(object sender, EventArgs e) { RefreshMaximizeRestoreButton(); } + #region Shortcut + + private void OnDeleteCustomShortCutClick(object sender, RoutedEventArgs e) + { + viewModel.DeleteSelectedCustomShortcut(); + } + + private void OnEditCustomShortCutClick(object sender, RoutedEventArgs e) + { + if (viewModel.EditSelectedCustomShortcut()) + { + customShortcutView.Items.Refresh(); + } + } + + private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e) + { + viewModel.AddCustomShortcut(); + } + + #endregion + private CollectionView pluginListView; private CollectionView pluginStoreView; @@ -381,7 +452,7 @@ namespace Flow.Launcher { if (string.IsNullOrEmpty(pluginStoreFilterTxb.Text)) return true; - if (item is UserPlugin model) + if (item is PluginStoreItemViewModel model) { return StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Name).IsSearchPrecisionScoreMet() || StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Description).IsSearchPrecisionScoreMet(); @@ -436,6 +507,34 @@ namespace Flow.Launcher } } + private void PreviewClockAndDate(object sender, RoutedEventArgs e) + { + ClockDisplay(); + } + + public void ClockDisplay() + { + if (settings.UseClock) + { + ClockBox.Visibility = Visibility.Visible; + ClockBox.Text = DateTime.Now.ToString(settings.TimeFormat); + } + else + { + ClockBox.Visibility = Visibility.Collapsed; + } + + if (settings.UseDate) + { + DateBox.Visibility = Visibility.Visible; + DateBox.Text = DateTime.Now.ToString(settings.DateFormat); + } + else + { + DateBox.Visibility = Visibility.Collapsed; + } + } + public void InitializePosition() { if (settings.SettingWindowTop >= 0 && settings.SettingWindowLeft >= 0) @@ -448,6 +547,7 @@ namespace Flow.Launcher Top = WindowTop(); Left = WindowLeft(); } + WindowState = settings.SettingWindowState; } public double WindowLeft() { @@ -466,16 +566,21 @@ namespace Flow.Launcher var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20; return top; } - private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e) + + private Button storeClickedButton; + + private void StoreListItem_Click(object sender, RoutedEventArgs e) { - if (e.ChangedButton == MouseButton.Left) + if (sender is not Button button) + return; + + storeClickedButton = button; + + var flyout = FlyoutService.GetFlyout(button); + flyout.Closed += (_, _) => { - var id = viewModel.SelectedPlugin.PluginPair.Metadata.Name; - var pluginsManagerPlugin = PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"); - var actionKeyword = pluginsManagerPlugin.Metadata.ActionKeywords.Count == 0 ? "" : pluginsManagerPlugin.Metadata.ActionKeywords[0]; - API.ChangeQuery($"{actionKeyword} uninstall {id}"); - API.ShowMainWindow(); - } + storeClickedButton = null; + }; } } diff --git a/Flow.Launcher/Themes/Atom.xaml b/Flow.Launcher/Themes/Atom.xaml index 10daf817f..f532c97d5 100644 --- a/Flow.Launcher/Themes/Atom.xaml +++ b/Flow.Launcher/Themes/Atom.xaml @@ -1,64 +1,100 @@ - + - - - - - - + - - - - #2c313c - - - - - - - + + diff --git a/Flow.Launcher/Themes/Base.xaml b/Flow.Launcher/Themes/Base.xaml index 5459a9464..77b0fd1c5 100644 --- a/Flow.Launcher/Themes/Base.xaml +++ b/Flow.Launcher/Themes/Base.xaml @@ -79,6 +79,83 @@ + + + + + + + @@ -36,6 +37,7 @@ x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}"> + @@ -140,4 +142,18 @@ + + diff --git a/Flow.Launcher/Themes/BlurWhite.xaml b/Flow.Launcher/Themes/BlurWhite.xaml index 1b939f736..6308f9e47 100644 --- a/Flow.Launcher/Themes/BlurWhite.xaml +++ b/Flow.Launcher/Themes/BlurWhite.xaml @@ -50,7 +50,7 @@ TargetType="{x:Type Window}"> - + @@ -143,4 +143,18 @@ + + diff --git a/Flow.Launcher/Themes/Darker Glass.xaml b/Flow.Launcher/Themes/Darker Glass.xaml index de37dd192..13c9e2bc5 100644 --- a/Flow.Launcher/Themes/Darker Glass.xaml +++ b/Flow.Launcher/Themes/Darker Glass.xaml @@ -1,84 +1,131 @@ - + - - - - - - + - - - #545454 - - - - - - + + diff --git a/Flow.Launcher/Themes/Darker.xaml b/Flow.Launcher/Themes/Darker.xaml index 675092bf8..b255c4d9e 100644 --- a/Flow.Launcher/Themes/Darker.xaml +++ b/Flow.Launcher/Themes/Darker.xaml @@ -1,51 +1,97 @@ - + - - - - + + + - - - #4d4d4d - - - + + + diff --git a/Flow.Launcher/Themes/Discord Dark.xaml b/Flow.Launcher/Themes/Discord Dark.xaml index 636fe7b6d..92b8d8da7 100644 --- a/Flow.Launcher/Themes/Discord Dark.xaml +++ b/Flow.Launcher/Themes/Discord Dark.xaml @@ -1,64 +1,101 @@ - + - - - - - - - - - - - - #49443c - - - - - - + + diff --git a/Flow.Launcher/Themes/Dracula.xaml b/Flow.Launcher/Themes/Dracula.xaml index c661d33e3..146038ba9 100644 --- a/Flow.Launcher/Themes/Dracula.xaml +++ b/Flow.Launcher/Themes/Dracula.xaml @@ -1,64 +1,100 @@ - + - - - - - - + - - - - #44475a - - - - - - - + + diff --git a/Flow.Launcher/Themes/Gray.xaml b/Flow.Launcher/Themes/Gray.xaml index c3cec7bd8..1cacc8ec2 100644 --- a/Flow.Launcher/Themes/Gray.xaml +++ b/Flow.Launcher/Themes/Gray.xaml @@ -1,61 +1,107 @@ - + - - - - - - - - - - #787878 - - - + + + diff --git a/Flow.Launcher/Themes/League.xaml b/Flow.Launcher/Themes/League.xaml index ac774c07b..771d38c39 100644 --- a/Flow.Launcher/Themes/League.xaml +++ b/Flow.Launcher/Themes/League.xaml @@ -118,5 +118,16 @@ - + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Metro Server.xaml b/Flow.Launcher/Themes/Metro Server.xaml index 016e6af9d..cbd70dbaf 100644 --- a/Flow.Launcher/Themes/Metro Server.xaml +++ b/Flow.Launcher/Themes/Metro Server.xaml @@ -1,48 +1,83 @@ - + - - - - - - - - - - + + #04152E - - - + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Nord Darker.xaml b/Flow.Launcher/Themes/Nord Darker.xaml index 413effa51..7c29eda22 100644 --- a/Flow.Launcher/Themes/Nord Darker.xaml +++ b/Flow.Launcher/Themes/Nord Darker.xaml @@ -1,64 +1,110 @@ - + - - - - - - - - - - - #4e586b - - - + + diff --git a/Flow.Launcher/Themes/Nord.xaml b/Flow.Launcher/Themes/Nord.xaml index 2fcc11ef2..735041656 100644 --- a/Flow.Launcher/Themes/Nord.xaml +++ b/Flow.Launcher/Themes/Nord.xaml @@ -1,63 +1,109 @@ - + - - - - - - - - - - - #596479 - - - + + diff --git a/Flow.Launcher/Themes/Pink.xaml b/Flow.Launcher/Themes/Pink.xaml index cb5f56a8e..7e1ae911a 100644 --- a/Flow.Launcher/Themes/Pink.xaml +++ b/Flow.Launcher/Themes/Pink.xaml @@ -1,63 +1,102 @@ - + - + - - - - - - - - - - + + #cc1081 - - + @@ -74,4 +113,16 @@ + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Sublime.xaml b/Flow.Launcher/Themes/Sublime.xaml index 82fff06ff..1b6f25e83 100644 --- a/Flow.Launcher/Themes/Sublime.xaml +++ b/Flow.Launcher/Themes/Sublime.xaml @@ -1,64 +1,101 @@ - + - - - - - - - - - - - - #3c454e - - - - - - + + diff --git a/Flow.Launcher/Themes/Win10Light.xaml b/Flow.Launcher/Themes/Win10Light.xaml index 6f487a895..ad16f390e 100644 --- a/Flow.Launcher/Themes/Win10Light.xaml +++ b/Flow.Launcher/Themes/Win10Light.xaml @@ -1,67 +1,104 @@ - + - - - - - - - - - - - - #ccd0d4 - - - - - - + + diff --git a/Flow.Launcher/Themes/Win11Dark.xaml b/Flow.Launcher/Themes/Win11Dark.xaml index 31f6fb956..aa510acaf 100644 --- a/Flow.Launcher/Themes/Win11Dark.xaml +++ b/Flow.Launcher/Themes/Win11Dark.xaml @@ -156,4 +156,16 @@ + + diff --git a/Flow.Launcher/Themes/Win11Light.xaml b/Flow.Launcher/Themes/Win11Light.xaml index 48f6820c4..df6d20bb4 100644 --- a/Flow.Launcher/Themes/Win11Light.xaml +++ b/Flow.Launcher/Themes/Win11Light.xaml @@ -164,4 +164,16 @@ + + diff --git a/Flow.Launcher/Themes/Win11System.xaml b/Flow.Launcher/Themes/Win11System.xaml index bcc83be9f..3cf7fd123 100644 --- a/Flow.Launcher/Themes/Win11System.xaml +++ b/Flow.Launcher/Themes/Win11System.xaml @@ -156,4 +156,16 @@ + + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 57c791f5e..99d2a0990 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -17,6 +17,7 @@ using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using Flow.Launcher.Infrastructure.Logger; using Microsoft.VisualStudio.Threading; +using System.Text; using System.Threading.Channels; using ISavable = Flow.Launcher.Plugin.ISavable; using System.IO; @@ -38,7 +39,6 @@ namespace Flow.Launcher.ViewModel private readonly FlowLauncherJsonStorage _historyItemsStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorage _topMostRecordStorage; - internal readonly Settings _settings; private readonly History _history; private readonly UserSelectedRecord _userSelectedRecord; private readonly TopMostRecord _topMostRecord; @@ -61,8 +61,8 @@ namespace Flow.Launcher.ViewModel _queryText = ""; _lastQuery = new Query(); - _settings = settings; - _settings.PropertyChanged += (_, args) => + Settings = settings; + Settings.PropertyChanged += (_, args) => { if (args.PropertyName == nameof(Settings.WindowSize)) { @@ -77,15 +77,26 @@ namespace Flow.Launcher.ViewModel _userSelectedRecord = _userSelectedRecordStorage.Load(); _topMostRecord = _topMostRecordStorage.Load(); - ContextMenu = new ResultsViewModel(_settings); - Results = new ResultsViewModel(_settings); - History = new ResultsViewModel(_settings); + InitializeKeyCommands(); + + ContextMenu = new ResultsViewModel(Settings) + { + LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand + }; + Results = new ResultsViewModel(Settings) + { + LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand + }; + History = new ResultsViewModel(Settings) + { + LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand + }; _selectedResults = Results; - InitializeKeyCommands(); RegisterViewUpdate(); RegisterResultsUpdatedEvent(); + RegisterClockAndDateUpdateAsync(); SetOpenResultModifiers(); } @@ -198,15 +209,10 @@ namespace Flow.Launcher.ViewModel PluginManager.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher/wiki/Flow-Launcher/"); }); OpenSettingCommand = new RelayCommand(_ => { App.API.OpenSettingDialog(); }); - OpenResultCommand = new RelayCommand(async index => + OpenResultCommand = new AsyncRelayCommand(async _ => { var results = SelectedResults; - if (index != null) - { - results.SelectedIndex = int.Parse(index.ToString()!); - } - var result = results.SelectedItem?.Result; if (result == null) { @@ -321,6 +327,22 @@ namespace Flow.Launcher.ViewModel #region ViewModel Properties + public Settings Settings { get; } + public object ClockText { get; private set; } + 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) + ClockText = DateTime.Now.ToString(Settings.TimeFormat); + if (Settings.UseDate) + DateText = DateTime.Now.ToString(Settings.DateFormat); + } + } public ResultsViewModel Results { get; private set; } public ResultsViewModel ContextMenu { get; private set; } @@ -344,14 +366,14 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void IncreaseWidth() { - if (MainWindowWidth + 100 > 1920 || _settings.WindowSize == 1920) + if (MainWindowWidth + 100 > 1920 || Settings.WindowSize == 1920) { - _settings.WindowSize = 1920; + Settings.WindowSize = 1920; } - else - { - _settings.WindowSize += 100; - _settings.WindowLeft -= 50; + else + { + Settings.WindowSize += 100; + Settings.WindowLeft -= 50; } OnPropertyChanged(); } @@ -359,14 +381,14 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void DecreaseWidth() { - if (MainWindowWidth - 100 < 400 || _settings.WindowSize == 400) + if (MainWindowWidth - 100 < 400 || Settings.WindowSize == 400) { - _settings.WindowSize = 400; + Settings.WindowSize = 400; } else - { - _settings.WindowLeft += 50; - _settings.WindowSize -= 100; + { + Settings.WindowLeft += 50; + Settings.WindowSize -= 100; } OnPropertyChanged(); } @@ -374,19 +396,19 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void IncreaseMaxResult() { - if (_settings.MaxResultsToShow == 17) + if (Settings.MaxResultsToShow == 17) return; - _settings.MaxResultsToShow += 1; + Settings.MaxResultsToShow += 1; } [RelayCommand] private void DecreaseMaxResult() { - if (_settings.MaxResultsToShow == 2) + if (Settings.MaxResultsToShow == 2) return; - _settings.MaxResultsToShow -= 1; + Settings.MaxResultsToShow -= 1; } /// @@ -466,8 +488,8 @@ namespace Flow.Launcher.ViewModel public double MainWindowWidth { - get => _settings.WindowSize; - set => _settings.WindowSize = value; + get => Settings.WindowSize; + set => Settings.WindowSize = value; } public string PluginIconPath { get; set; } = null; @@ -605,7 +627,9 @@ namespace Flow.Launcher.ViewModel { _updateSource?.Cancel(); - if (string.IsNullOrWhiteSpace(QueryText)) + var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts); + + if (query == null) // shortcut expanded { Results.Clear(); Results.Visbility = Visibility.Collapsed; @@ -630,7 +654,6 @@ namespace Flow.Launcher.ViewModel if (currentCancellationToken.IsCancellationRequested) return; - var query = QueryBuilder.Build(QueryText, PluginManager.NonGlobalPlugins); // handle the exclusiveness of plugin using action keyword RemoveOldQueryResults(query); @@ -720,6 +743,40 @@ namespace Flow.Launcher.ViewModel } } + private Query ConstructQuery(string queryText, IEnumerable customShortcuts, IEnumerable builtInShortcuts) + { + if (string.IsNullOrWhiteSpace(queryText)) + { + return null; + } + + StringBuilder queryBuilder = new(queryText); + StringBuilder queryBuilderTmp = new(queryText); + + foreach (var shortcut in customShortcuts) + { + if (queryBuilder.Equals(shortcut.Key)) + { + queryBuilder.Replace(shortcut.Key, shortcut.Expand()); + } + + queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand()); + } + + foreach (var shortcut in builtInShortcuts) + { + queryBuilder.Replace(shortcut.Key, shortcut.Expand()); + queryBuilderTmp.Replace(shortcut.Key, shortcut.Expand()); + } + + // show expanded builtin shortcuts + // use private field to avoid infinite recursion + _queryText = queryBuilderTmp.ToString(); + + var query = QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins); + return query; + } + private void RemoveOldQueryResults(Query query) { if (_lastQuery?.ActionKeyword != query?.ActionKeyword) @@ -816,7 +873,7 @@ namespace Flow.Launcher.ViewModel private void SetOpenResultModifiers() { - OpenResultCommandModifiers = _settings.OpenResultModifiers ?? DefaultOpenResultModifiers; + OpenResultCommandModifiers = Settings.OpenResultModifiers ?? DefaultOpenResultModifiers; } public void ToggleFlowLauncher() @@ -845,24 +902,24 @@ namespace Flow.Launcher.ViewModel // Trick for no delay MainWindowOpacity = 0; - switch (_settings.LastQueryMode) + switch (Settings.LastQueryMode) { case LastQueryMode.Empty: ChangeQueryText(string.Empty); await Task.Delay(100); //Time for change to opacity break; case LastQueryMode.Preserved: - if (_settings.UseAnimation) + if (Settings.UseAnimation) await Task.Delay(100); LastQuerySelected = true; break; case LastQueryMode.Selected: - if (_settings.UseAnimation) + if (Settings.UseAnimation) await Task.Delay(100); LastQuerySelected = false; break; default: - throw new ArgumentException($"wrong LastQueryMode: <{_settings.LastQueryMode}>"); + throw new ArgumentException($"wrong LastQueryMode: <{Settings.LastQueryMode}>"); } MainWindowVisibilityStatus = false; @@ -877,7 +934,7 @@ namespace Flow.Launcher.ViewModel /// public bool ShouldIgnoreHotkeys() { - return _settings.IgnoreHotkeysOnFullscreen && WindowsInteropHelper.IsWindowFullscreen(); + return Settings.IgnoreHotkeysOnFullscreen && WindowsInteropHelper.IsWindowFullscreen(); } @@ -950,28 +1007,26 @@ namespace Flow.Launcher.ViewModel var result = Results.SelectedItem?.Result; if (result != null) { - string copyText = string.IsNullOrEmpty(result.CopyText) ? result.SubTitle : result.CopyText; + string copyText = result.CopyText; var isFile = File.Exists(copyText); var isFolder = Directory.Exists(copyText); if (isFile || isFolder) { - var paths = new StringCollection(); - paths.Add(copyText); + var paths = new StringCollection + { + copyText + }; Clipboard.SetFileDropList(paths); App.API.ShowMsg( - App.API.GetTranslation("copy") - + " " - + (isFile ? App.API.GetTranslation("fileTitle") : App.API.GetTranslation("folderTitle")), + $"{App.API.GetTranslation("copy")} {(isFile ? App.API.GetTranslation("fileTitle") : App.API.GetTranslation("folderTitle"))}", App.API.GetTranslation("completedSuccessfully")); } else { - Clipboard.SetDataObject(copyText.ToString()); + Clipboard.SetDataObject(copyText); App.API.ShowMsg( - App.API.GetTranslation("copy") - + " " - + App.API.GetTranslation("textTitle"), + $"{App.API.GetTranslation("copy")} {App.API.GetTranslation("textTitle")}", App.API.GetTranslation("completedSuccessfully")); } } diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs new file mode 100644 index 000000000..622e41b1b --- /dev/null +++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs @@ -0,0 +1,58 @@ +using System; +using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.ViewModel +{ + public class PluginStoreItemViewModel : BaseModel + { + public PluginStoreItemViewModel(UserPlugin plugin) + { + _plugin = plugin; + } + + private UserPlugin _plugin; + + public string ID => _plugin.ID; + public string Name => _plugin.Name; + public string Description => _plugin.Description; + public string Author => _plugin.Author; + public string Version => _plugin.Version; + public string Language => _plugin.Language; + public string Website => _plugin.Website; + public string UrlDownload => _plugin.UrlDownload; + public string UrlSourceCode => _plugin.UrlSourceCode; + public string IcoPath => _plugin.IcoPath; + + public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null; + public bool LabelUpdate => LabelInstalled && _plugin.Version != PluginManager.GetPluginForId(_plugin.ID).Metadata.Version; + + internal const string None = "None"; + internal const string RecentlyUpdated = "RecentlyUpdated"; + internal const string NewRelease = "NewRelease"; + internal const string Installed = "Installed"; + + public string Category + { + get + { + string category = None; + if (DateTime.Now - _plugin.LatestReleaseDate < TimeSpan.FromDays(7)) + { + category = RecentlyUpdated; + } + if (DateTime.Now - _plugin.DateAdded < TimeSpan.FromDays(7)) + { + category = NewRelease; + } + if (PluginManager.GetPluginForId(_plugin.ID) != null) + { + category = Installed; + } + + return category; + } + } + } +} diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index a188cd357..458aa498f 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -163,15 +163,17 @@ namespace Flow.Launcher.ViewModel } } + var loadFullImage = (Path.GetExtension(imagePath) ?? "").Equals(".url", StringComparison.OrdinalIgnoreCase); + if (ImageLoader.CacheContainImage(imagePath)) { // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - image = ImageLoader.Load(imagePath); + image = ImageLoader.Load(imagePath, loadFullImage); return; } // We need to modify the property not field here to trigger the OnPropertyChanged event - Image = await Task.Run(() => ImageLoader.Load(imagePath)).ConfigureAwait(false); + Image = await Task.Run(() => ImageLoader.Load(imagePath, loadFullImage)).ConfigureAwait(false); } public Result Result { get; } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index db24825d0..1820ea56b 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -8,6 +8,8 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; +using System.Windows.Input; +using JetBrains.Annotations; namespace Flow.Launcher.ViewModel { @@ -49,6 +51,9 @@ namespace Flow.Launcher.ViewModel public ResultViewModel SelectedItem { get; set; } public Thickness Margin { get; set; } public Visibility Visbility { get; set; } = Visibility.Collapsed; + + public ICommand RightClickResultCommand { get; init; } + public ICommand LeftClickResultCommand { get; init; } #endregion diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 1ef1edeb9..65321e6ba 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -19,10 +19,12 @@ using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.Input; namespace Flow.Launcher.ViewModel { - public class SettingWindowViewModel : BaseModel + public partial class SettingWindowViewModel : BaseModel { private readonly Updater _updater; private readonly IPortable _portable; @@ -212,11 +214,19 @@ namespace Flow.Launcher.ViewModel } } - public List OpenResultModifiersList => new List { KeyConstant.Alt, KeyConstant.Ctrl, $"{KeyConstant.Ctrl}+{KeyConstant.Alt}" }; + public List OpenResultModifiersList => new List + { + KeyConstant.Alt, + KeyConstant.Ctrl, + $"{KeyConstant.Ctrl}+{KeyConstant.Alt}" + }; private Internationalization _translater => InternationalizationManager.Instance; public List Languages => _translater.LoadAvailableLanguages(); public IEnumerable MaxResultsRange => Enumerable.Range(2, 16); + public ObservableCollection CustomShortcuts => Settings.CustomShortcuts; + public ObservableCollection BuiltinShortcuts => Settings.BuiltinShortcuts; + public string TestProxy() { var proxyServer = Settings.Proxy.Server; @@ -275,20 +285,33 @@ namespace Flow.Launcher.ViewModel var metadatas = PluginManager.AllPlugins .OrderBy(x => x.Metadata.Disabled) .ThenBy(y => y.Metadata.Name) - .Select(p => new PluginViewModel { PluginPair = p }) + .Select(p => new PluginViewModel + { + PluginPair = p + }) .ToList(); return metadatas; } } - public IList ExternalPlugins + public IList ExternalPlugins { get { - return PluginsManifest.UserPlugins; + return LabelMaker(PluginsManifest.UserPlugins); } } + private IList LabelMaker(IList list) + { + return list.Select(p=>new PluginStoreItemViewModel(p)) + .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease) + .ThenByDescending(p=>p.Category == PluginStoreItemViewModel.RecentlyUpdated) + .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None) + .ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed) + .ToList(); + } + public Control SettingProvider { get @@ -308,12 +331,22 @@ namespace Flow.Launcher.ViewModel } } - public async Task RefreshExternalPluginsAsync() + [RelayCommand] + private async Task RefreshExternalPluginsAsync() { await PluginsManifest.UpdateManifestAsync(); OnPropertyChanged(nameof(ExternalPlugins)); } + internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0) + { + var actionKeyword = plugin.Metadata.ActionKeywords.Count == 0 + ? string.Empty + : plugin.Metadata.ActionKeywords[actionKeywordPosition]; + + App.API.ChangeQuery($"{actionKeyword} {queryToDisplay}"); + App.API.ShowMainWindow(); + } #endregion @@ -378,7 +411,11 @@ namespace Flow.Launcher.ViewModel { var key = $"ColorScheme{e}"; var display = _translater.GetTranslation(key); - var m = new ColorScheme { Display = display, Value = e, }; + var m = new ColorScheme + { + Display = display, + Value = e, + }; modes.Add(m); } return modes; @@ -410,7 +447,24 @@ namespace Flow.Launcher.ViewModel } } + public List TimeFormatList { get; set; } = new List() + { + "hh:mm", + "HH:mm", + "tt hh:mm", + "hh:mm tt" + }; + public List DateFormatList { get; set; } = new List() + { + "MM'/'dd dddd", + "MM'/'dd ddd", + "MM'/'dd", + "dd'/'MM", + "ddd MM'/'dd", + "dddd MM'/'dd", + "dddd" + }; public double WindowWidthSize { @@ -436,6 +490,18 @@ namespace Flow.Launcher.ViewModel set => Settings.UseSound = value; } + public bool UseClock + { + get => Settings.UseClock; + set => Settings.UseClock = value; + } + + public bool UseDate + { + get => Settings.UseDate; + set => Settings.UseDate = value; + } + public double SettingWindowWidth { get => Settings.SettingWindowWidth; @@ -471,8 +537,13 @@ namespace Flow.Launcher.ViewModel var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.StreamSource = memStream; + bitmap.DecodePixelWidth = 800; + bitmap.DecodePixelHeight = 600; bitmap.EndInit(); - var brush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill }; + var brush = new ImageBrush(bitmap) + { + Stretch = Stretch.UniformToFill + }; return brush; } else @@ -500,19 +571,19 @@ namespace Flow.Launcher.ViewModel { Title = "WebSearch", SubTitle = "Search the web with different search engine support", - IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png") + IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png") }, new Result { Title = "Program", SubTitle = "Launch programs as admin or a different user", - IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png") + IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png") }, new Result { Title = "ProcessKiller", SubTitle = "Terminate unwanted processes", - IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png") + IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png") } }; var vm = new ResultsViewModel(Settings); @@ -526,8 +597,8 @@ namespace Flow.Launcher.ViewModel get { if (Fonts.SystemFontFamilies.Count(o => - o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0) + o.FamilyNames.Values != null && + o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0) { var font = new FontFamily(Settings.QueryBoxFont); return font; @@ -554,7 +625,7 @@ namespace Flow.Launcher.ViewModel Settings.QueryBoxFontStyle, Settings.QueryBoxFontWeight, Settings.QueryBoxFontStretch - )); + )); return typeface; } set @@ -571,8 +642,8 @@ namespace Flow.Launcher.ViewModel get { if (Fonts.SystemFontFamilies.Count(o => - o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0) + o.FamilyNames.Values != null && + o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0) { var font = new FontFamily(Settings.ResultFont); return font; @@ -599,7 +670,7 @@ namespace Flow.Launcher.ViewModel Settings.ResultFontStyle, Settings.ResultFontWeight, Settings.ResultFontStretch - )); + )); return typeface; } set @@ -621,6 +692,65 @@ namespace Flow.Launcher.ViewModel #endregion + #region shortcut + + public CustomShortcutModel? SelectedCustomShortcut { get; set; } + + public void DeleteSelectedCustomShortcut() + { + var item = SelectedCustomShortcut; + if (item == null) + { + MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); + return; + } + + string deleteWarning = string.Format( + InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), + item?.Key, item?.Value); + if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) + { + Settings.CustomShortcuts.Remove(item); + } + } + + public bool EditSelectedCustomShortcut() + { + var item = SelectedCustomShortcut; + if (item == null) + { + MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); + return false; + } + + var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this); + if (shortcutSettingWindow.ShowDialog() == true) + { + item.Key = shortcutSettingWindow.Key; + item.Value = shortcutSettingWindow.Value; + return true; + } + return false; + } + + public void AddCustomShortcut() + { + var shortcutSettingWindow = new CustomShortcutSetting(this); + if (shortcutSettingWindow.ShowDialog() == true) + { + var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value); + Settings.CustomShortcuts.Add(shortcut); + } + } + + public bool ShortcutExists(string key) + { + return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key); + } + + #endregion + #region about public string Website => Constant.Website; @@ -628,7 +758,20 @@ namespace Flow.Launcher.ViewModel public string Documentation => Constant.Documentation; public string Docs => Constant.Docs; public string Github => Constant.GitHub; - public static string Version => Constant.Version; + public string Version + { + get + { + if (Constant.Version == "1.0.0") + { + return Constant.Dev; + } + else + { + return Constant.Version; + } + } + } public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes); public string CheckLogFolder diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 892e18ddd..fc58e40df 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; +using Flow.Launcher.Plugin.BrowserBookmark.Models; using System; using System.Collections.Generic; using System.Data.SQLite; @@ -130,4 +130,4 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 8454b11e6..4596acb4c 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -3,7 +3,7 @@ Library net6.0-windows - true + true {9B130CC5-14FB-41FF-B310-0A95B6894C37} Properties Flow.Launcher.Plugin.BrowserBookmark @@ -23,7 +23,7 @@ 4 false - + pdbonly true @@ -39,15 +39,6 @@ PreserveNewest - - - - Always - - - Always - - @@ -64,8 +55,8 @@ - - + + @@ -73,4 +64,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/x64/SQLite.Interop.dll b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/x64/SQLite.Interop.dll deleted file mode 100644 index 877b4d78e..000000000 Binary files a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/x64/SQLite.Interop.dll and /dev/null differ diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/x86/SQLite.Interop.dll b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/x86/SQLite.Interop.dll deleted file mode 100644 index ccb5e03b6..000000000 Binary files a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/x86/SQLite.Interop.dll and /dev/null differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml index 195fc7df9..8397145cf 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml @@ -6,12 +6,13 @@ xmlns:local="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="{DynamicResource plugin_explorer_manageactionkeywords_header}" - Width="400" - SizeToContent="Height" + Width="Auto" + Height="255" Background="{DynamicResource PopuBGColor}" DataContext="{Binding RelativeSource={RelativeSource Self}}" Foreground="{DynamicResource PopupTextColor}" ResizeMode="NoResize" + SizeToContent="Width" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> @@ -68,7 +69,7 @@ - LoadContextMenus(Result selectedResult) @@ -74,4 +74,4 @@ namespace Flow.Launcher.Plugin.PluginsManager return Context.API.GetTranslation("plugin_pluginsmanager_plugin_description"); } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml b/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml index 9230eb062..b1a8744fd 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml @@ -5,12 +5,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="{DynamicResource flowlauncher_plugin_program_directory}" - Width="400" + Width="Auto" + Height="276" Background="{DynamicResource PopuBGColor}" Foreground="{DynamicResource PopupTextColor}" - SizeToContent="Height" - WindowStartupLocation="CenterScreen" - mc:Ignorable="d"> + ResizeMode="NoResize" + SizeToContent="Width" + WindowStartupLocation="CenterScreen"> @@ -19,7 +20,6 @@ - @@ -53,33 +53,80 @@ - - + + - - -