Merge 'dev' into 'velopack'

This commit is contained in:
VictoriousRaptor 2024-05-03 19:54:03 +08:00
commit f9bb3e6aa7
161 changed files with 4644 additions and 2673 deletions

View file

@ -58,7 +58,7 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
############################### ###############################
# Style Definitions # Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields # Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
@ -134,7 +134,7 @@ csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
csharp_using_directive_placement = outside_namespace:silent csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = block_scoped:silent csharp_style_namespace_declarations = file_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_method_group_conversion = true:silent
csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent csharp_style_expression_bodied_local_functions = false:silent

View file

@ -21,3 +21,6 @@ vkcode
winget winget
workaround workaround
nupkg nupkg
Firefox
Português
Português (Brasil)

View file

@ -74,6 +74,7 @@ WCA_ACCENT_POLICY
HGlobal HGlobal
dopusrt dopusrt
firefox firefox
Firefox
msedge msedge
svgc svgc
ime ime
@ -97,6 +98,7 @@ Português
Português (Brasil) Português (Brasil)
Italiano Italiano
Slovenský Slovenský
Tiếng Việt
Droplex Droplex
Preinstalled Preinstalled
errormetadatafile errormetadatafile

View file

@ -65,7 +65,7 @@ jobs:
- name: Publish BrowserBookmark - name: Publish BrowserBookmark
if: steps.changes.outputs.browserbookmark == 'true' if: steps.changes.outputs.browserbookmark == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.BrowserBookmark" repository: "Flow-Launcher/Flow.Launcher.Plugin.BrowserBookmark"
files: "Flow.Launcher.Plugin.BrowserBookmark.zip" files: "Flow.Launcher.Plugin.BrowserBookmark.zip"
@ -92,7 +92,7 @@ jobs:
- name: Publish Calculator - name: Publish Calculator
if: steps.changes.outputs.calculator == 'true' if: steps.changes.outputs.calculator == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Calculator" repository: "Flow-Launcher/Flow.Launcher.Plugin.Calculator"
files: "Flow.Launcher.Plugin.Calculator.zip" files: "Flow.Launcher.Plugin.Calculator.zip"
@ -119,7 +119,7 @@ jobs:
- name: Publish Explorer - name: Publish Explorer
if: steps.changes.outputs.explorer == 'true' if: steps.changes.outputs.explorer == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Explorer" repository: "Flow-Launcher/Flow.Launcher.Plugin.Explorer"
files: "Flow.Launcher.Plugin.Explorer.zip" files: "Flow.Launcher.Plugin.Explorer.zip"
@ -146,7 +146,7 @@ jobs:
- name: Publish PluginIndicator - name: Publish PluginIndicator
if: steps.changes.outputs.pluginindicator == 'true' if: steps.changes.outputs.pluginindicator == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginIndicator" repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginIndicator"
files: "Flow.Launcher.Plugin.PluginIndicator.zip" files: "Flow.Launcher.Plugin.PluginIndicator.zip"
@ -173,7 +173,7 @@ jobs:
- name: Publish PluginsManager - name: Publish PluginsManager
if: steps.changes.outputs.pluginsmanager == 'true' if: steps.changes.outputs.pluginsmanager == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginsManager" repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginsManager"
files: "Flow.Launcher.Plugin.PluginsManager.zip" files: "Flow.Launcher.Plugin.PluginsManager.zip"
@ -200,7 +200,7 @@ jobs:
- name: Publish ProcessKiller - name: Publish ProcessKiller
if: steps.changes.outputs.processkiller == 'true' if: steps.changes.outputs.processkiller == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller" repository: "Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller"
files: "Flow.Launcher.Plugin.ProcessKiller.zip" files: "Flow.Launcher.Plugin.ProcessKiller.zip"
@ -227,7 +227,7 @@ jobs:
- name: Publish Program - name: Publish Program
if: steps.changes.outputs.program == 'true' if: steps.changes.outputs.program == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Program" repository: "Flow-Launcher/Flow.Launcher.Plugin.Program"
files: "Flow.Launcher.Plugin.Program.zip" files: "Flow.Launcher.Plugin.Program.zip"
@ -254,7 +254,7 @@ jobs:
- name: Publish Shell - name: Publish Shell
if: steps.changes.outputs.shell == 'true' if: steps.changes.outputs.shell == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Shell" repository: "Flow-Launcher/Flow.Launcher.Plugin.Shell"
files: "Flow.Launcher.Plugin.Shell.zip" files: "Flow.Launcher.Plugin.Shell.zip"
@ -281,7 +281,7 @@ jobs:
- name: Publish Sys - name: Publish Sys
if: steps.changes.outputs.sys == 'true' if: steps.changes.outputs.sys == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Sys" repository: "Flow-Launcher/Flow.Launcher.Plugin.Sys"
files: "Flow.Launcher.Plugin.Sys.zip" files: "Flow.Launcher.Plugin.Sys.zip"
@ -308,7 +308,7 @@ jobs:
- name: Publish Url - name: Publish Url
if: steps.changes.outputs.url == 'true' if: steps.changes.outputs.url == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Url" repository: "Flow-Launcher/Flow.Launcher.Plugin.Url"
files: "Flow.Launcher.Plugin.Url.zip" files: "Flow.Launcher.Plugin.Url.zip"
@ -335,7 +335,7 @@ jobs:
- name: Publish WebSearch - name: Publish WebSearch
if: steps.changes.outputs.websearch == 'true' if: steps.changes.outputs.websearch == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.WebSearch" repository: "Flow-Launcher/Flow.Launcher.Plugin.WebSearch"
files: "Flow.Launcher.Plugin.WebSearch.zip" files: "Flow.Launcher.Plugin.WebSearch.zip"
@ -362,7 +362,7 @@ jobs:
- name: Publish WindowsSettings - name: Publish WindowsSettings
if: steps.changes.outputs.windowssettings == 'true' if: steps.changes.outputs.windowssettings == 'true'
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.WindowsSettings" repository: "Flow-Launcher/Flow.Launcher.Plugin.WindowsSettings"
files: "Flow.Launcher.Plugin.WindowsSettings.zip" files: "Flow.Launcher.Plugin.WindowsSettings.zip"

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Flow.Launcher.Core.Resource namespace Flow.Launcher.Core.Resource
{ {
@ -27,6 +27,8 @@ namespace Flow.Launcher.Core.Resource
public static Language Turkish = new Language("tr", "Türkçe"); public static Language Turkish = new Language("tr", "Türkçe");
public static Language Czech = new Language("cs", "čeština"); public static Language Czech = new Language("cs", "čeština");
public static Language Arabic = new Language("ar", "اللغة العربية"); public static Language Arabic = new Language("ar", "اللغة العربية");
public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt");
public static List<Language> GetAvailableLanguages() public static List<Language> GetAvailableLanguages()
{ {
@ -54,7 +56,8 @@ namespace Flow.Launcher.Core.Resource
Slovak, Slovak,
Turkish, Turkish,
Czech, Czech,
Arabic Arabic,
Vietnamese
}; };
return languages; return languages;
} }

View file

@ -7,6 +7,7 @@ namespace Flow.Launcher.Infrastructure
public static class Constant public static class Constant
{ {
public const string FlowLauncher = "Flow.Launcher"; public const string FlowLauncher = "Flow.Launcher";
public const string FlowLauncherFullName = "Flow Launcher";
public const string Plugins = "Plugins"; public const string Plugins = "Plugins";
public const string PluginMetadataFileName = "plugin.json"; public const string PluginMetadataFileName = "plugin.json";

View file

@ -54,7 +54,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MemoryPack" Version="1.10.0" /> <PackageReference Include="MemoryPack" Version="1.21.1" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.7.30" /> <PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.7.30" />
<PackageReference Include="NLog" Version="4.7.10" /> <PackageReference Include="NLog" Version="4.7.10" />
<PackageReference Include="NLog.Schema" Version="4.7.10" /> <PackageReference Include="NLog.Schema" Version="4.7.10" />

View file

@ -6,7 +6,7 @@ using System.Windows.Input;
namespace Flow.Launcher.Infrastructure.Hotkey namespace Flow.Launcher.Infrastructure.Hotkey
{ {
public class HotkeyModel public record struct HotkeyModel
{ {
public bool Alt { get; set; } public bool Alt { get; set; }
public bool Shift { get; set; } public bool Shift { get; set; }
@ -17,8 +17,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey
private static readonly Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string> private static readonly Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
{ {
{Key.Space, "Space"}, { Key.Space, "Space" }, { Key.Oem3, "~" }
{Key.Oem3, "~"}
}; };
public ModifierKeys ModifierKeys public ModifierKeys ModifierKeys
@ -30,18 +29,22 @@ namespace Flow.Launcher.Infrastructure.Hotkey
{ {
modifierKeys |= ModifierKeys.Alt; modifierKeys |= ModifierKeys.Alt;
} }
if (Shift) if (Shift)
{ {
modifierKeys |= ModifierKeys.Shift; modifierKeys |= ModifierKeys.Shift;
} }
if (Win) if (Win)
{ {
modifierKeys |= ModifierKeys.Windows; modifierKeys |= ModifierKeys.Windows;
} }
if (Ctrl) if (Ctrl)
{ {
modifierKeys |= ModifierKeys.Control; modifierKeys |= ModifierKeys.Control;
} }
return modifierKeys; return modifierKeys;
} }
} }
@ -66,31 +69,37 @@ namespace Flow.Launcher.Infrastructure.Hotkey
{ {
return; return;
} }
List<string> keys = hotkeyString.Replace(" ", "").Split('+').ToList(); List<string> keys = hotkeyString.Replace(" ", "").Split('+').ToList();
if (keys.Contains("Alt")) if (keys.Contains("Alt"))
{ {
Alt = true; Alt = true;
keys.Remove("Alt"); keys.Remove("Alt");
} }
if (keys.Contains("Shift")) if (keys.Contains("Shift"))
{ {
Shift = true; Shift = true;
keys.Remove("Shift"); keys.Remove("Shift");
} }
if (keys.Contains("Win")) if (keys.Contains("Win"))
{ {
Win = true; Win = true;
keys.Remove("Win"); keys.Remove("Win");
} }
if (keys.Contains("Ctrl")) if (keys.Contains("Ctrl"))
{ {
Ctrl = true; Ctrl = true;
keys.Remove("Ctrl"); keys.Remove("Ctrl");
} }
if (keys.Count == 1) if (keys.Count == 1)
{ {
string charKey = keys[0]; string charKey = keys[0];
KeyValuePair<Key, string>? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); KeyValuePair<Key, string>? specialSymbolPair =
specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey);
if (specialSymbolPair.Value.Value != null) if (specialSymbolPair.Value.Value != null)
{ {
CharKey = specialSymbolPair.Value.Key; CharKey = specialSymbolPair.Value.Key;
@ -103,7 +112,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey
} }
catch (ArgumentException) catch (ArgumentException)
{ {
} }
} }
} }
@ -111,33 +119,39 @@ namespace Flow.Launcher.Infrastructure.Hotkey
public override string ToString() public override string ToString()
{ {
List<string> keys = new List<string>(); return string.Join(" + ", EnumerateDisplayKeys());
if (Ctrl) }
public IEnumerable<string> EnumerateDisplayKeys()
{
if (Ctrl && CharKey is not (Key.LeftCtrl or Key.RightCtrl))
{ {
keys.Add("Ctrl"); yield return "Ctrl";
} }
if (Alt)
if (Alt && CharKey is not (Key.LeftAlt or Key.RightAlt))
{ {
keys.Add("Alt"); yield return "Alt";
} }
if (Shift)
if (Shift && CharKey is not (Key.LeftShift or Key.RightShift))
{ {
keys.Add("Shift"); yield return "Shift";
} }
if (Win)
if (Win && CharKey is not (Key.LWin or Key.RWin))
{ {
keys.Add("Win"); yield return "Win";
} }
if (CharKey != Key.None) if (CharKey != Key.None)
{ {
keys.Add(specialSymbolDictionary.ContainsKey(CharKey) yield return specialSymbolDictionary.TryGetValue(CharKey, out var value)
? specialSymbolDictionary[CharKey] ? value
: CharKey.ToString()); : CharKey.ToString();
} }
return string.Join(" + ", keys);
} }
/// <summary> /// <summary>
/// Validate hotkey /// Validate hotkey
/// </summary> /// </summary>
@ -164,11 +178,13 @@ namespace Flow.Launcher.Infrastructure.Hotkey
{ {
KeyGesture keyGesture = new KeyGesture(CharKey, ModifierKeys); KeyGesture keyGesture = new KeyGesture(CharKey, ModifierKeys);
} }
catch (System.Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) catch (System.Exception e) when
(e is NotSupportedException || e is InvalidEnumArgumentException)
{ {
return false; return false;
} }
} }
if (ModifierKeys == ModifierKeys.None) if (ModifierKeys == ModifierKeys.None)
{ {
return !IsPrintableCharacter(CharKey); return !IsPrintableCharacter(CharKey);
@ -206,18 +222,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey
key == Key.Decimal; key == Key.Decimal;
} }
public override bool Equals(object obj)
{
if (obj is HotkeyModel other)
{
return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey;
}
else
{
return false;
}
}
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(ModifierKeys, CharKey); return HashCode.Combine(ModifierKeys, CharKey);

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Flow.Launcher.Infrastructure.Hotkey;
/// <summary>
/// Interface that you should implement in your settings class to be able to pass it to
/// <c>Flow.Launcher.HotkeyControlDialog</c>. It allows the dialog to display the hotkeys that have already been
/// registered, and optionally provide a way to unregister them.
/// </summary>
public interface IHotkeySettings
{
/// <summary>
/// A list of hotkeys that have already been registered. The dialog will display these hotkeys and provide a way to
/// unregister them.
/// </summary>
public List<RegisteredHotkeyData> RegisteredHotkeys { get; }
}

View file

@ -0,0 +1,119 @@
using System;
namespace Flow.Launcher.Infrastructure.Hotkey;
#nullable enable
/// <summary>
/// Represents a hotkey that has been registered. Used in <c>Flow.Launcher.HotkeyControlDialog</c> via
/// <see cref="UserSettings"/> and <see cref="IHotkeySettings"/> to display errors if user tries to register a hotkey
/// that has already been registered, and optionally provides a way to unregister the hotkey.
/// </summary>
public record RegisteredHotkeyData
{
/// <summary>
/// <see cref="HotkeyModel"/> representation of this hotkey.
/// </summary>
public HotkeyModel Hotkey { get; }
/// <summary>
/// String key in the localization dictionary that represents this hotkey. For example, <c>ReloadPluginHotkey</c>,
/// which represents the string "Reload Plugins Data" in <c>en.xaml</c>
/// </summary>
public string DescriptionResourceKey { get; }
/// <summary>
/// Array of values that will replace <c>{0}</c>, <c>{1}</c>, <c>{2}</c>, etc. in the localized string found via
/// <see cref="DescriptionResourceKey"/>.
/// </summary>
public object?[] DescriptionFormatVariables { get; } = Array.Empty<object?>();
/// <summary>
/// An action that, when called, will unregister this hotkey. If it's <c>null</c>, it's assumed that
/// this hotkey can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
/// </summary>
public Action? RemoveHotkey { get; }
/// <summary>
/// Creates an instance of <c>RegisteredHotkeyData</c>. Assumes that the key specified in
/// <c>descriptionResourceKey</c> doesn't need any arguments for <c>string.Format</c>. If it does,
/// use one of the other constructors.
/// </summary>
/// <param name="hotkey">
/// The hotkey this class will represent.
/// Example values: <c>F1</c>, <c>Ctrl+Shift+Enter</c>
/// </param>
/// <param name="descriptionResourceKey">
/// The key in the localization dictionary that represents this hotkey. For example, <c>ReloadPluginHotkey</c>,
/// which represents the string "Reload Plugins Data" in <c>en.xaml</c>
/// </param>
/// <param name="removeHotkey">
/// An action that, when called, will unregister this hotkey. If it's <c>null</c>, it's assumed that this hotkey
/// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
/// </param>
public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action? removeHotkey = null)
{
Hotkey = new HotkeyModel(hotkey);
DescriptionResourceKey = descriptionResourceKey;
RemoveHotkey = removeHotkey;
}
/// <summary>
/// Creates an instance of <c>RegisteredHotkeyData</c>. Assumes that the key specified in
/// <c>descriptionResourceKey</c> needs exactly one argument for <c>string.Format</c>.
/// </summary>
/// <param name="hotkey">
/// The hotkey this class will represent.
/// Example values: <c>F1</c>, <c>Ctrl+Shift+Enter</c>
/// </param>
/// <param name="descriptionResourceKey">
/// The key in the localization dictionary that represents this hotkey. For example, <c>ReloadPluginHotkey</c>,
/// which represents the string "Reload Plugins Data" in <c>en.xaml</c>
/// </param>
/// <param name="descriptionFormatVariable">
/// The value that will replace <c>{0}</c> in the localized string found via <c>description</c>.
/// </param>
/// <param name="removeHotkey">
/// An action that, when called, will unregister this hotkey. If it's <c>null</c>, it's assumed that this hotkey
/// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
/// </param>
public RegisteredHotkeyData(
string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, Action? removeHotkey = null
)
{
Hotkey = new HotkeyModel(hotkey);
DescriptionResourceKey = descriptionResourceKey;
DescriptionFormatVariables = new[] { descriptionFormatVariable };
RemoveHotkey = removeHotkey;
}
/// <summary>
/// Creates an instance of <c>RegisteredHotkeyData</c>. Assumes that the key specified in
/// <paramref name="descriptionResourceKey"/> needs multiple arguments for <c>string.Format</c>.
/// </summary>
/// <param name="hotkey">
/// The hotkey this class will represent.
/// Example values: <c>F1</c>, <c>Ctrl+Shift+Enter</c>
/// </param>
/// <param name="descriptionResourceKey">
/// The key in the localization dictionary that represents this hotkey. For example, <c>ReloadPluginHotkey</c>,
/// which represents the string "Reload Plugins Data" in <c>en.xaml</c>
/// </param>
/// <param name="descriptionFormatVariables">
/// Array of values that will replace <c>{0}</c>, <c>{1}</c>, <c>{2}</c>, etc.
/// in the localized string found via <c>description</c>.
/// </param>
/// <param name="removeHotkey">
/// An action that, when called, will unregister this hotkey. If it's <c>null</c>, it's assumed that this hotkey
/// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
/// </param>
public RegisteredHotkeyData(
string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, Action? removeHotkey = null
)
{
Hotkey = new HotkeyModel(hotkey);
DescriptionResourceKey = descriptionResourceKey;
DescriptionFormatVariables = descriptionFormatVariables;
RemoveHotkey = removeHotkey;
}
}

View file

@ -4,13 +4,14 @@ using System.Collections.ObjectModel;
using System.Drawing; using System.Drawing;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Windows; using System.Windows;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public class Settings : BaseModel public class Settings : BaseModel, IHotkeySettings
{ {
private string language = "en"; private string language = "en";
private string _theme = Constant.DefaultTheme; private string _theme = Constant.DefaultTheme;
@ -20,6 +21,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool ShowOpenResultHotkey { get; set; } = true; public bool ShowOpenResultHotkey { get; set; } = true;
public double WindowSize { get; set; } = 580; public double WindowSize { get; set; } = 580;
public string PreviewHotkey { get; set; } = $"F1"; public string PreviewHotkey { get; set; } = $"F1";
public string AutoCompleteHotkey { get; set; } = $"{KeyConstant.Ctrl} + Tab";
public string AutoCompleteHotkey2 { get; set; } = $"";
public string SelectNextItemHotkey { get; set; } = $"Tab";
public string SelectNextItemHotkey2 { get; set; } = $"";
public string SelectPrevItemHotkey { get; set; } = $"Shift + Tab";
public string SelectPrevItemHotkey2 { get; set; } = $"";
public string SelectNextPageHotkey { get; set; } = $"PageUp";
public string SelectPrevPageHotkey { get; set; } = $"PageDown";
public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O";
public string SettingWindowHotkey { get; set; } = $"Ctrl+I";
public string Language public string Language
{ {
@ -54,6 +65,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool UseGlyphIcons { get; set; } = true; public bool UseGlyphIcons { get; set; } = true;
public bool UseAnimation { get; set; } = true; public bool UseAnimation { get; set; } = true;
public bool UseSound { get; set; } = true; public bool UseSound { get; set; } = true;
public double SoundVolume { get; set; } = 50;
public bool UseClock { get; set; } = true; public bool UseClock { get; set; } = true;
public bool UseDate { get; set; } = false; public bool UseDate { get; set; } = false;
public string TimeFormat { get; set; } = "hh:mm tt"; public string TimeFormat { get; set; } = "hh:mm tt";
@ -195,17 +208,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double WindowLeft { get; set; } public double WindowLeft { get; set; }
public double WindowTop { get; set; } public double WindowTop { get; set; }
/// <summary> /// <summary>
/// Custom left position on selected monitor /// Custom left position on selected monitor
/// </summary> /// </summary>
public double CustomWindowLeft { get; set; } = 0; public double CustomWindowLeft { get; set; } = 0;
/// <summary> /// <summary>
/// Custom top position on selected monitor /// Custom top position on selected monitor
/// </summary> /// </summary>
public double CustomWindowTop { get; set; } = 0; public double CustomWindowTop { get; set; } = 0;
public int MaxResultsToShow { get; set; } = 5; public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; } public int ActivateTimes { get; set; }
@ -217,7 +230,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonIgnore] [JsonIgnore]
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts { get; set; } = new() public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts { get; set; } = new()
{ {
new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath) new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath)
}; };
@ -241,7 +254,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonConverter(typeof(JsonStringEnumConverter))] [JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor; public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;
[JsonConverter(typeof(JsonStringEnumConverter))] [JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center; public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center;
@ -261,6 +274,82 @@ namespace Flow.Launcher.Infrastructure.UserSettings
// This needs to be loaded last by staying at the bottom // This needs to be loaded last by staying at the bottom
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
[JsonIgnore]
public List<RegisteredHotkeyData> RegisteredHotkeys
{
get
{
var list = new List<RegisteredHotkeyData>
{
new("Up", "HotkeyLeftRightDesc"),
new("Down", "HotkeyLeftRightDesc"),
new("Left", "HotkeyUpDownDesc"),
new("Right", "HotkeyUpDownDesc"),
new("Escape", "HotkeyESCDesc"),
new("F5", "ReloadPluginHotkey"),
new("Alt+Home", "HotkeySelectFirstResult"),
new("Alt+End", "HotkeySelectLastResult"),
new("Ctrl+R", "HotkeyRequery"),
new("Ctrl+H", "ToggleHistoryHotkey"),
new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"),
new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"),
new("Ctrl+OemPlus", "QuickHeightHotkey"),
new("Ctrl+OemMinus", "QuickHeightHotkey"),
new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"),
new("Shift+Enter", "OpenContextMenuHotkey"),
new("Enter", "HotkeyRunDesc"),
new("Ctrl+Enter", "OpenContainFolderHotkey"),
new("Alt+Enter", "HotkeyOpenResult"),
new("Ctrl+F12", "ToggleGameModeHotkey"),
new("Ctrl+Shift+C", "CopyFilePathHotkey"),
new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1),
new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2),
new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3),
new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4),
new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5),
new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6),
new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7),
new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8),
new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9),
new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10)
};
if(!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if(!string.IsNullOrEmpty(PreviewHotkey))
list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = ""));
if(!string.IsNullOrEmpty(AutoCompleteHotkey))
list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = ""));
if(!string.IsNullOrEmpty(AutoCompleteHotkey2))
list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = ""));
if(!string.IsNullOrEmpty(SelectNextItemHotkey))
list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = ""));
if(!string.IsNullOrEmpty(SelectNextItemHotkey2))
list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = ""));
if(!string.IsNullOrEmpty(SelectPrevItemHotkey))
list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = ""));
if(!string.IsNullOrEmpty(SelectPrevItemHotkey2))
list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = ""));
if(!string.IsNullOrEmpty(SettingWindowHotkey))
list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = ""));
if(!string.IsNullOrEmpty(OpenContextMenuHotkey))
list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = ""));
if(!string.IsNullOrEmpty(SelectNextPageHotkey))
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
if(!string.IsNullOrEmpty(SelectPrevPageHotkey))
list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = ""));
foreach (var customPluginHotkey in CustomPluginHotkeys)
{
if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = ""));
}
return list;
}
}
} }
public enum LastQueryMode public enum LastQueryMode
@ -276,7 +365,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Light, Light,
Dark Dark
} }
public enum SearchWindowScreens public enum SearchWindowScreens
{ {
RememberLastLaunchLocation, RememberLastLaunchLocation,
@ -285,7 +374,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Primary, Primary,
Custom Custom
} }
public enum SearchWindowAligns public enum SearchWindowAligns
{ {
Center, Center,

View file

@ -14,10 +14,10 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>4.2.0</Version> <Version>4.3.0</Version>
<PackageVersion>4.2.0</PackageVersion> <PackageVersion>4.3.0</PackageVersion>
<AssemblyVersion>4.2.0</AssemblyVersion> <AssemblyVersion>4.3.0</AssemblyVersion>
<FileVersion>4.2.0</FileVersion> <FileVersion>4.3.0</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId> <PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors> <Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>

View file

@ -291,5 +291,12 @@ namespace Flow.Launcher.Plugin
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public bool IsGameModeOn(); public bool IsGameModeOn();
/// <summary>
/// Reloads the query.
/// This method should run
/// </summary>
/// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param>
public void ReQuery(bool reselect = true);
} }
} }

View file

@ -183,6 +183,31 @@ namespace Flow.Launcher.Plugin
return Title + SubTitle + Score; return Title + SubTitle + Score;
} }
/// <summary>
/// Clones the current result
/// </summary>
public Result Clone()
{
return new Result
{
Title = Title,
SubTitle = SubTitle,
ActionKeywordAssigned = ActionKeywordAssigned,
CopyText = CopyText,
AutoCompleteText = AutoCompleteText,
IcoPath = IcoPath,
RoundedIcon = RoundedIcon,
Icon = Icon,
Glyph = Glyph,
Action = Action,
AsyncAction = AsyncAction,
Score = Score,
TitleHighlightData = TitleHighlightData,
OriginQuery = OriginQuery,
PluginDirectory = PluginDirectory
};
}
/// <summary> /// <summary>
/// Additional data associated with this result /// Additional data associated with this result
/// </summary> /// </summary>

View file

@ -3,39 +3,38 @@ using System.Globalization;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
{
internal class BoolToIMEConversionModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
true => ImeConversionModeValues.Alphanumeric,
_ => ImeConversionModeValues.DoNotCare
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) internal class BoolToIMEConversionModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{ {
throw new NotImplementedException(); true => ImeConversionModeValues.Alphanumeric,
} _ => ImeConversionModeValues.DoNotCare
};
} }
internal class BoolToIMEStateConverter : IValueConverter public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) throw new NotImplementedException();
{ }
return value switch }
{
true => InputMethodState.Off, internal class BoolToIMEStateConverter : IValueConverter
_ => InputMethodState.DoNotCare {
}; public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
} {
return value switch
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
{ true => InputMethodState.Off,
throw new NotImplementedException(); _ => InputMethodState.DoNotCare
} };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
} }
} }

View file

@ -2,40 +2,39 @@
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
public class BoolToVisibilityConverter : IValueConverter
{ {
public class BoolToVisibilityConverter : IValueConverter public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)
{ {
public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) return (value, parameter) switch
{ {
return (value, parameter) switch (true, not null) => Visibility.Collapsed,
{ (_, not null) => Visibility.Visible,
(true, not null) => Visibility.Collapsed,
(_, not null) => Visibility.Visible,
(true, null) => Visibility.Visible, (true, null) => Visibility.Visible,
(_, null) => Visibility.Collapsed (_, null) => Visibility.Collapsed
}; };
}
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
} }
public class SplitterConverter : IValueConverter public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
/* Prevents the dragging part of the preview area from working when preview is turned off. */ }
{
public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) public class SplitterConverter : IValueConverter
{ /* Prevents the dragging part of the preview area from working when preview is turned off. */
return (value, parameter) switch {
{ public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)
(true, not null) => 0, {
(_, not null) => 5, return (value, parameter) switch
{
(true, null) => 5, (true, not null) => 0,
(_, null) => 0 (_, not null) => 5,
};
} (true, null) => 5,
(_, null) => 0
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); };
} }
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
} }

View file

@ -7,43 +7,41 @@ using System.Windows.Shapes;
// For Clipping inside listbox item // For Clipping inside listbox item
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
public class BorderClipConverter : IMultiValueConverter
{ {
public class BorderClipConverter : IMultiValueConverter public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{ {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) if (values is not [double width, double height, CornerRadius radius])
{ {
if (values is not [double width, double height, CornerRadius radius]) return DependencyProperty.UnsetValue;
{
return DependencyProperty.UnsetValue;
}
Path myPath = new Path();
if (width < Double.Epsilon || height < Double.Epsilon)
{
return Geometry.Empty;
}
var radiusHeight = radius.TopLeft;
// Drawing Round box for bottom round, and rect for top area of listbox.
var corner = new RectangleGeometry(new Rect(0, 0, width, height), radius.TopLeft, radius.TopLeft);
var box = new RectangleGeometry(new Rect(0, 0, width, radiusHeight), 0, 0);
GeometryGroup myGeometryGroup = new GeometryGroup();
myGeometryGroup.Children.Add(corner);
myGeometryGroup.Children.Add(box);
CombinedGeometry c1 = new CombinedGeometry(GeometryCombineMode.Union, corner, box);
myPath.Data = c1;
myPath.Data.Freeze();
return myPath.Data;
} }
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) Path myPath = new Path();
if (width < Double.Epsilon || height < Double.Epsilon)
{ {
throw new NotSupportedException(); return Geometry.Empty;
} }
var radiusHeight = radius.TopLeft;
// Drawing Round box for bottom round, and rect for top area of listbox.
var corner = new RectangleGeometry(new Rect(0, 0, width, height), radius.TopLeft, radius.TopLeft);
var box = new RectangleGeometry(new Rect(0, 0, width, radiusHeight), 0, 0);
GeometryGroup myGeometryGroup = new GeometryGroup();
myGeometryGroup.Children.Add(corner);
myGeometryGroup.Children.Add(box);
CombinedGeometry c1 = new CombinedGeometry(GeometryCombineMode.Union, corner, box);
myPath.Data = c1;
myPath.Data.Freeze();
return myPath.Data;
} }
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
} }

View file

@ -2,18 +2,17 @@
using System.Globalization; using System.Globalization;
using System.Windows.Data; using System.Windows.Data;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
{
public class DateTimeFormatToNowConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public class DateTimeFormatToNowConverter : IValueConverter
{ {
return value is not string format ? null : DateTime.Now.ToString(format);
} public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
{ return value is not string format ? null : DateTime.Now.ToString(format);
throw new NotImplementedException(); }
} public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
} }
} }

View file

@ -3,23 +3,22 @@ using System.Globalization;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
public class DiameterToCenterPointConverter : IValueConverter
{ {
public class DiameterToCenterPointConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) if (value is double d)
{ {
if (value is double d) return new Point(d / 2, d / 2);
{
return new Point(d / 2, d / 2);
}
return new Point(0, 0);
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) return new Point(0, 0);
{ }
throw new NotSupportedException();
} public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
} }
} }

View file

@ -5,45 +5,44 @@ using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Documents; using System.Windows.Documents;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
public class HighlightTextConverter : IMultiValueConverter
{ {
public class HighlightTextConverter : IMultiValueConverter public object Convert(object[] value, Type targetType, object parameter, CultureInfo cultureInfo)
{ {
public object Convert(object[] value, Type targetType, object parameter, CultureInfo cultureInfo) if (value.Length < 2)
{ return new Run(string.Empty);
if (value.Length < 2)
return new Run(string.Empty);
if (value[0] is not string text)
return new Run(string.Empty);
if (value[1] is not List<int> { Count: > 0 } highlightData)
// No highlight data, just return the text
return new Run(text);
var highlightStyle = (Style)Application.Current.FindResource("HighlightStyle");
var textBlock = new Span();
for (var i = 0; i < text.Length; i++) if (value[0] is not string text)
return new Run(string.Empty);
if (value[1] is not List<int> { Count: > 0 } highlightData)
// No highlight data, just return the text
return new Run(text);
var highlightStyle = (Style)Application.Current.FindResource("HighlightStyle");
var textBlock = new Span();
for (var i = 0; i < text.Length; i++)
{
var currentCharacter = text.Substring(i, 1);
var run = new Run(currentCharacter)
{ {
var currentCharacter = text.Substring(i, 1); Style = ShouldHighlight(highlightData, i) ? highlightStyle : null
var run = new Run(currentCharacter) };
{ textBlock.Inlines.Add(run);
Style = ShouldHighlight(highlightData, i) ? highlightStyle : null
};
textBlock.Inlines.Add(run);
}
return textBlock;
} }
return textBlock;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{ {
return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue }; return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue };
} }
private bool ShouldHighlight(List<int> highlightData, int index) private bool ShouldHighlight(List<int> highlightData, int index)
{ {
return highlightData.Contains(index); return highlightData.Contains(index);
}
} }
} }

View file

@ -2,20 +2,19 @@
using System.Globalization; using System.Globalization;
using System.Windows.Data; using System.Windows.Data;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
{
public class IconRadiusConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values is not [double size, bool isIconCircular])
throw new ArgumentException("IconRadiusConverter must have 2 parameters: [double, bool]");
return isIconCircular ? size / 2 : size; public class IconRadiusConverter : IMultiValueConverter
} {
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{ {
throw new NotSupportedException(); if (values is not [double size, bool isIconCircular])
} throw new ArgumentException("IconRadiusConverter must have 2 parameters: [double, bool]");
return isIconCircular ? size / 2 : size;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
} }
} }

View file

@ -4,24 +4,23 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using System.Windows.Data;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
[ValueConversion(typeof(bool), typeof(Visibility))]
public class OpenResultHotkeyVisibilityConverter : IValueConverter
{ {
[ValueConversion(typeof(bool), typeof(Visibility))] private const int MaxVisibleHotkeys = 10;
public class OpenResultHotkeyVisibilityConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
private const int MaxVisibleHotkeys = 10; var number = int.MaxValue;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) if (value is ListBoxItem listBoxItem
{ && ItemsControl.ItemsControlFromItemContainer(listBoxItem) is ListBox listBox)
var number = int.MaxValue; number = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1;
if (value is ListBoxItem listBoxItem return number <= MaxVisibleHotkeys ? Visibility.Visible : Visibility.Collapsed;
&& ItemsControl.ItemsControlFromItemContainer(listBoxItem) is ListBox listBox)
number = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1;
return number <= MaxVisibleHotkeys ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException();
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException();
} }

View file

@ -3,23 +3,22 @@ using System.Globalization;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using System.Windows.Data;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
public class OrdinalConverter : IValueConverter
{ {
public class OrdinalConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) if (value is not ListBoxItem listBoxItem
|| ItemsControl.ItemsControlFromItemContainer(listBoxItem) is not ListBox listBox)
{ {
if (value is not ListBoxItem listBoxItem return 0;
|| ItemsControl.ItemsControlFromItemContainer(listBoxItem) is not ListBox listBox)
{
return 0;
}
var res = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1;
return res == 10 ? 0 : res; // 10th item => HOTKEY+0
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException(); var res = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1;
return res == 10 ? 0 : res; // 10th item => HOTKEY+0
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException();
} }

View file

@ -6,63 +6,62 @@ using System.Windows.Media;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
public class QuerySuggestionBoxConverter : IMultiValueConverter
{ {
public class QuerySuggestionBoxConverter : IMultiValueConverter public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{ {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) // values[0] is TextBox: The textbox displaying the autocomplete suggestion
// values[1] is ResultViewModel: Currently selected item in the list
// values[2] is string: Query text
if (
values.Length != 3 ||
values[0] is not TextBox queryTextBox ||
values[1] is null ||
values[2] is not string queryText ||
string.IsNullOrEmpty(queryText)
)
return string.Empty;
if (values[1] is not ResultViewModel selectedItem)
return Binding.DoNothing;
try
{ {
// values[0] is TextBox: The textbox displaying the autocomplete suggestion var selectedResult = selectedItem.Result;
// values[1] is ResultViewModel: Currently selected item in the list var selectedResultActionKeyword = string.IsNullOrEmpty(selectedResult.ActionKeywordAssigned) ? "" : selectedResult.ActionKeywordAssigned + " ";
// values[2] is string: Query text var selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title;
if (
values.Length != 3 || if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))
values[0] is not TextBox queryTextBox ||
values[1] is null ||
values[2] is not string queryText ||
string.IsNullOrEmpty(queryText)
)
return string.Empty; return string.Empty;
if (values[1] is not ResultViewModel selectedItem)
return Binding.DoNothing;
try
{
var selectedResult = selectedItem.Result;
var selectedResultActionKeyword = string.IsNullOrEmpty(selectedResult.ActionKeywordAssigned) ? "" : selectedResult.ActionKeywordAssigned + " ";
var selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title;
if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))
return string.Empty;
// For AutocompleteQueryCommand. // For AutocompleteQueryCommand.
// When user typed lower case and result title is uppercase, we still want to display suggestion // When user typed lower case and result title is uppercase, we still want to display suggestion
selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length); selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length);
// Check if Text will be larger than our QueryTextBox // Check if Text will be larger than our QueryTextBox
Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch); Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch);
// TODO: Obsolete warning? // TODO: Obsolete warning?
var ft = new FormattedText(queryTextBox.Text, CultureInfo.DefaultThreadCurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, queryTextBox.FontSize, Brushes.Black); var ft = new FormattedText(queryTextBox.Text, CultureInfo.DefaultThreadCurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, queryTextBox.FontSize, Brushes.Black);
var offset = queryTextBox.Padding.Right; var offset = queryTextBox.Padding.Right;
if (ft.Width + offset > queryTextBox.ActualWidth || queryTextBox.HorizontalOffset != 0) if (ft.Width + offset > queryTextBox.ActualWidth || queryTextBox.HorizontalOffset != 0)
return string.Empty;
return selectedItem.QuerySuggestionText;
}
catch (Exception e)
{
Log.Exception(nameof(QuerySuggestionBoxConverter), "fail to convert text for suggestion box", e);
return string.Empty; return string.Empty;
}
return selectedItem.QuerySuggestionText;
} }
catch (Exception e)
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); Log.Exception(nameof(QuerySuggestionBoxConverter), "fail to convert text for suggestion box", e);
return string.Empty;
} }
} }
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
} }

View file

@ -3,28 +3,27 @@ using System.Globalization;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
{
class StringToKeyBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is not string mode || value is not string hotkeyStr)
return null;
var converter = new KeyGestureConverter();
var key = (KeyGesture)converter.ConvertFromString(hotkeyStr);
return mode switch
{
"key" => key?.Key,
"modifiers" => key?.Modifiers,
_ => null
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) class StringToKeyBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is not string mode || value is not string hotkeyStr)
return null;
var converter = new KeyGestureConverter();
var key = (KeyGesture)converter.ConvertFromString(hotkeyStr);
return mode switch
{ {
throw new NotImplementedException(); "key" => key?.Key,
} "modifiers" => key?.Modifiers,
_ => null
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
} }
} }

View file

@ -4,28 +4,27 @@ using System.Windows.Data;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Converters namespace Flow.Launcher.Converters;
{
public class TextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var id = value?.ToString();
var translationKey = id switch
{
PluginStoreItemViewModel.NewRelease => "pluginStore_NewRelease",
PluginStoreItemViewModel.RecentlyUpdated => "pluginStore_RecentlyUpdated",
PluginStoreItemViewModel.None => "pluginStore_None",
PluginStoreItemViewModel.Installed => "pluginStore_Installed",
_ => null
};
if (translationKey is null)
return id;
return InternationalizationManager.Instance.GetTranslation(translationKey);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException(); public class TextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var id = value?.ToString();
var translationKey = id switch
{
PluginStoreItemViewModel.NewRelease => "pluginStore_NewRelease",
PluginStoreItemViewModel.RecentlyUpdated => "pluginStore_RecentlyUpdated",
PluginStoreItemViewModel.None => "pluginStore_None",
PluginStoreItemViewModel.Installed => "pluginStore_Installed",
_ => null
};
if (translationKey is null)
return id;
return InternationalizationManager.Instance.GetTranslation(translationKey);
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException();
} }

View file

@ -2,10 +2,12 @@
x:Class="Flow.Launcher.CustomQueryHotkeySetting" x:Class="Flow.Launcher.CustomQueryHotkeySetting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flowlauncher="clr-namespace:Flow.Launcher" xmlns:flowlauncher="clr-namespace:Flow.Launcher"
Title="{DynamicResource customeQueryHotkeyTitle}" Title="{DynamicResource customeQueryHotkeyTitle}"
Width="530" Width="530"
Background="{DynamicResource PopuBGColor}" Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}" Foreground="{DynamicResource PopupTextColor}"
Icon="Images\app.png" Icon="Images\app.png"
MouseDown="window_MouseDown" MouseDown="window_MouseDown"
@ -60,94 +62,76 @@
</Grid> </Grid>
</StackPanel> </StackPanel>
<StackPanel Margin="26,0,26,0"> <StackPanel Margin="26,0,26,0">
<StackPanel Grid.Row="0" Margin="0,0,0,12"> <TextBlock
<TextBlock Margin="0,0,0,12"
Grid.Column="0" FontSize="20"
Margin="0,0,0,0" FontWeight="SemiBold"
FontSize="20" Text="{DynamicResource customeQueryHotkeyTitle}"
FontWeight="SemiBold" TextAlignment="Left" />
Text="{DynamicResource customeQueryHotkeyTitle}" <TextBlock
TextAlignment="Left" /> FontSize="14"
</StackPanel> Text="{DynamicResource customeQueryHotkeyTips}"
<StackPanel> TextAlignment="Left"
<TextBlock TextWrapping="WrapWithOverflow" />
FontSize="14" <Image
Text="{DynamicResource customeQueryHotkeyTips}" Width="478"
TextAlignment="Left" Margin="0,20,0,0"
TextWrapping="WrapWithOverflow" /> Source="/Images/illustration_01.png" />
<Image
Width="478"
Margin="0,20,0,0"
Source="/Images/illustration_01.png" />
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal"> <Grid Width="478" Margin="0,20,0,0">
<Grid Width="478"> <Grid.RowDefinitions>
<Grid.RowDefinitions> <RowDefinition />
<RowDefinition /> <RowDefinition />
<RowDefinition /> </Grid.RowDefinitions>
</Grid.RowDefinitions> <Grid.ColumnDefinitions>
<Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0" <TextBlock
Grid.Column="0" Grid.Row="0"
Margin="10" Grid.Column="0"
HorizontalAlignment="Left" Margin="10"
VerticalAlignment="Center" HorizontalAlignment="Left"
FontSize="14" VerticalAlignment="Center"
Text="{DynamicResource hotkey}" /> FontSize="14"
<StackPanel Text="{DynamicResource hotkey}" />
Grid.Row="0" <flowlauncher:HotkeyControl
Grid.Column="1" x:Name="HotkeyControl"
Orientation="Horizontal"> Grid.Row="0"
<flowlauncher:HotkeyControl Grid.Column="1"
x:Name="ctlHotkey" Grid.ColumnSpan="2"
Grid.Column="1" Margin="10,0,10,0"
Width="200" HorizontalAlignment="Left"
Height="36" VerticalAlignment="Center"
Margin="10,0,10,0" HorizontalContentAlignment="Left"
HorizontalAlignment="Left" HotkeySettings="{Binding Settings}"
VerticalAlignment="Center" DefaultHotkey="" />
HorizontalContentAlignment="Left" /> <TextBlock
<TextBlock Grid.Row="1"
Grid.Row="1" Grid.Column="0"
Grid.Column="0" Margin="10"
Margin="10" HorizontalAlignment="Left"
HorizontalAlignment="Left" VerticalAlignment="Center"
VerticalAlignment="Center" FontSize="14"
FontSize="14" Text="{DynamicResource customQuery}" />
Text="{DynamicResource actionKeyword}" /> <TextBox
</StackPanel> x:Name="tbAction"
<TextBlock Grid.Row="1"
Grid.Row="1" Grid.Column="1"
Grid.Column="0" Margin="10"
Margin="10" HorizontalAlignment="Stretch"
HorizontalAlignment="Left" VerticalAlignment="Center" />
VerticalAlignment="Center" <Button
FontSize="14" x:Name="btnTestActionKeyword"
Text="{DynamicResource customQuery}" /> Grid.Row="1"
<DockPanel Grid.Column="2"
Grid.Row="1" Margin="0,0,10,0"
Grid.Column="1" Padding="10,5,10,5"
LastChildFill="True"> Click="BtnTestActionKeyword_OnClick"
<Button Content="{DynamicResource preview}" />
x:Name="btnTestActionKeyword" </Grid>
Margin="0,0,10,0"
Padding="10,5,10,5"
Click="BtnTestActionKeyword_OnClick"
Content="{DynamicResource preview}"
DockPanel.Dock="Right" />
<TextBox
x:Name="tbAction"
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
</DockPanel>
</Grid>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<Border <Border
@ -174,4 +158,4 @@
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>
</Window> </Window>

View file

@ -6,6 +6,7 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Controls; using System.Windows.Controls;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher namespace Flow.Launcher
{ {
@ -14,13 +15,13 @@ namespace Flow.Launcher
private SettingWindow _settingWidow; private SettingWindow _settingWidow;
private bool update; private bool update;
private CustomPluginHotkey updateCustomHotkey; private CustomPluginHotkey updateCustomHotkey;
private Settings _settings; public Settings Settings { get; }
public CustomQueryHotkeySetting(SettingWindow settingWidow, Settings settings) public CustomQueryHotkeySetting(SettingWindow settingWidow, Settings settings)
{ {
_settingWidow = settingWidow; _settingWidow = settingWidow;
Settings = settings;
InitializeComponent(); InitializeComponent();
_settings = settings;
} }
private void BtnCancel_OnClick(object sender, RoutedEventArgs e) private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
@ -32,36 +33,21 @@ namespace Flow.Launcher
{ {
if (!update) if (!update)
{ {
if (!ctlHotkey.CurrentHotkeyAvailable) Settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>();
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("hotkeyIsNotUnavailable"));
return;
}
if (_settings.CustomPluginHotkeys == null)
{
_settings.CustomPluginHotkeys = new ObservableCollection<CustomPluginHotkey>();
}
var pluginHotkey = new CustomPluginHotkey var pluginHotkey = new CustomPluginHotkey
{ {
Hotkey = ctlHotkey.CurrentHotkey.ToString(), Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
ActionKeyword = tbAction.Text
}; };
_settings.CustomPluginHotkeys.Add(pluginHotkey); Settings.CustomPluginHotkeys.Add(pluginHotkey);
HotKeyMapper.SetCustomQueryHotkey(pluginHotkey); HotKeyMapper.SetCustomQueryHotkey(pluginHotkey);
} }
else else
{ {
if (updateCustomHotkey.Hotkey != ctlHotkey.CurrentHotkey.ToString() && !ctlHotkey.CurrentHotkeyAvailable)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("hotkeyIsNotUnavailable"));
return;
}
var oldHotkey = updateCustomHotkey.Hotkey; var oldHotkey = updateCustomHotkey.Hotkey;
updateCustomHotkey.ActionKeyword = tbAction.Text; updateCustomHotkey.ActionKeyword = tbAction.Text;
updateCustomHotkey.Hotkey = ctlHotkey.CurrentHotkey.ToString(); updateCustomHotkey.Hotkey = HotkeyControl.CurrentHotkey.ToString();
//remove origin hotkey //remove origin hotkey
HotKeyMapper.RemoveHotkey(oldHotkey); HotKeyMapper.RemoveHotkey(oldHotkey);
HotKeyMapper.SetCustomQueryHotkey(updateCustomHotkey); HotKeyMapper.SetCustomQueryHotkey(updateCustomHotkey);
@ -70,9 +56,11 @@ namespace Flow.Launcher
Close(); Close();
} }
public void UpdateItem(CustomPluginHotkey item) public void UpdateItem(CustomPluginHotkey item)
{ {
updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o => o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); updateCustomHotkey = Settings.CustomPluginHotkeys.FirstOrDefault(o =>
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (updateCustomHotkey == null) if (updateCustomHotkey == null)
{ {
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey")); MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey"));
@ -81,7 +69,7 @@ namespace Flow.Launcher
} }
tbAction.Text = updateCustomHotkey.ActionKeyword; tbAction.Text = updateCustomHotkey.ActionKeyword;
_ = ctlHotkey.SetHotkeyAsync(updateCustomHotkey.Hotkey, false); HotkeyControl.SetHotkey(updateCustomHotkey.Hotkey, false);
update = true; update = true;
lblAdd.Text = InternationalizationManager.Instance.GetTranslation("update"); lblAdd.Text = InternationalizationManager.Instance.GetTranslation("update");
} }
@ -101,12 +89,10 @@ namespace Flow.Launcher
private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
{ {
TextBox textBox = Keyboard.FocusedElement as TextBox; if (Keyboard.FocusedElement is not TextBox textBox) return;
if (textBox != null)
{ TraversalRequest tRequest = new TraversalRequest(FocusNavigationDirection.Next);
TraversalRequest tRequest = new TraversalRequest(FocusNavigationDirection.Next); textBox.MoveFocus(tRequest);
textBox.MoveFocus(tRequest);
}
} }
} }
} }

View file

@ -3,57 +3,56 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Microsoft.Win32; using Microsoft.Win32;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
public class AutoStartup
{ {
public class AutoStartup private const string StartupPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
public static bool IsEnabled
{ {
private const string StartupPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"; get
public static bool IsEnabled
{
get
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
var path = key?.GetValue(Constant.FlowLauncher) as string;
return path == Constant.ExecutablePath;
}
catch (Exception e)
{
Log.Error("AutoStartup", $"Ignoring non-critical registry error (querying if enabled): {e}");
}
return false;
}
}
public static void Disable()
{ {
try try
{ {
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
key?.DeleteValue(Constant.FlowLauncher, false); var path = key?.GetValue(Constant.FlowLauncher) as string;
return path == Constant.ExecutablePath;
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error("AutoStartup", $"Failed to disable auto-startup: {e}"); Log.Error("AutoStartup", $"Ignoring non-critical registry error (querying if enabled): {e}");
throw;
} }
}
internal static void Enable() return false;
}
}
public static void Disable()
{
try
{ {
try using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
{ key?.DeleteValue(Constant.FlowLauncher, false);
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); }
key?.SetValue(Constant.FlowLauncher, $"\"{Constant.ExecutablePath}\""); catch (Exception e)
} {
catch (Exception e) Log.Error("AutoStartup", $"Failed to disable auto-startup: {e}");
{ throw;
Log.Error("AutoStartup", $"Failed to enable auto-startup: {e}"); }
throw; }
}
internal static void Enable()
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
key?.SetValue(Constant.FlowLauncher, $"\"{Constant.ExecutablePath}\"");
}
catch (Exception e)
{
Log.Error("AutoStartup", $"Failed to enable auto-startup: {e}");
throw;
} }
} }
} }

View file

@ -4,70 +4,69 @@ using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
public class DwmDropShadow
{ {
public class DwmDropShadow
[DllImport("dwmapi.dll", PreserveSig = true)]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset);
/// <summary>
/// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven).
/// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect,
/// as AllowsTransparency involves a huge permormance issue (hardware acceleration is turned off for all the window).
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
public static void DropShadowToWindow(Window window)
{ {
if (!DropShadow(window))
[DllImport("dwmapi.dll", PreserveSig = true)]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset);
/// <summary>
/// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven).
/// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect,
/// as AllowsTransparency involves a huge permormance issue (hardware acceleration is turned off for all the window).
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
public static void DropShadowToWindow(Window window)
{ {
if (!DropShadow(window)) window.SourceInitialized += window_SourceInitialized;
{
window.SourceInitialized += window_SourceInitialized;
}
} }
}
private static void window_SourceInitialized(object sender, EventArgs e) //fixed typo private static void window_SourceInitialized(object sender, EventArgs e) //fixed typo
{
Window window = (Window)sender;
DropShadow(window);
window.SourceInitialized -= window_SourceInitialized;
}
/// <summary>
/// The actual method that makes API calls to drop the shadow to the window
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
/// <returns>True if the method succeeded, false if not</returns>
private static bool DropShadow(Window window)
{
try
{ {
Window window = (Window)sender; WindowInteropHelper helper = new WindowInteropHelper(window);
int val = 2;
int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4);
DropShadow(window); if (ret1 == 0)
window.SourceInitialized -= window_SourceInitialized;
}
/// <summary>
/// The actual method that makes API calls to drop the shadow to the window
/// </summary>
/// <param name="window">Window to which the shadow will be applied</param>
/// <returns>True if the method succeeded, false if not</returns>
private static bool DropShadow(Window window)
{
try
{ {
WindowInteropHelper helper = new WindowInteropHelper(window); Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 };
int val = 2; int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4); return ret2 == 0;
if (ret1 == 0)
{
Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 };
int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
return ret2 == 0;
}
else
{
return false;
}
} }
catch (Exception) else
{ {
// Probably dwmapi.dll not found (incompatible OS)
return false; return false;
} }
} }
catch (Exception)
{
// Probably dwmapi.dll not found (incompatible OS)
return false;
}
} }
}
}

View file

@ -2,68 +2,67 @@
using System.IO; using System.IO;
using System.Net; using System.Net;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
public class DataWebRequestFactory : IWebRequestCreate
{ {
public class DataWebRequestFactory : IWebRequestCreate class DataWebRequest : WebRequest
{ {
class DataWebRequest : WebRequest private readonly Uri _uri;
public DataWebRequest(Uri uri)
{ {
private readonly Uri m_uri; _uri = uri;
public DataWebRequest(Uri uri)
{
m_uri = uri;
}
public override WebResponse GetResponse()
{
return new DataWebResponse(m_uri);
}
} }
class DataWebResponse : WebResponse public override WebResponse GetResponse()
{ {
private readonly string m_contentType; return new DataWebResponse(_uri);
private readonly byte[] m_data;
public DataWebResponse(Uri uri)
{
string uriString = uri.AbsoluteUri;
int commaIndex = uriString.IndexOf(',');
var headers = uriString.Substring(0, commaIndex).Split(';');
m_contentType = headers[0];
string dataString = uriString.Substring(commaIndex + 1);
m_data = Convert.FromBase64String(dataString);
}
public override string ContentType
{
get { return m_contentType; }
set
{
throw new NotSupportedException();
}
}
public override long ContentLength
{
get { return m_data.Length; }
set
{
throw new NotSupportedException();
}
}
public override Stream GetResponseStream()
{
return new MemoryStream(m_data);
}
}
public WebRequest Create(Uri uri)
{
return new DataWebRequest(uri);
} }
} }
class DataWebResponse : WebResponse
{
private readonly string _contentType;
private readonly byte[] _data;
public DataWebResponse(Uri uri)
{
string uriString = uri.AbsoluteUri;
int commaIndex = uriString.IndexOf(',');
var headers = uriString.Substring(0, commaIndex).Split(';');
_contentType = headers[0];
string dataString = uriString.Substring(commaIndex + 1);
_data = Convert.FromBase64String(dataString);
}
public override string ContentType
{
get { return _contentType; }
set
{
throw new NotSupportedException();
}
}
public override long ContentLength
{
get { return _data.Length; }
set
{
throw new NotSupportedException();
}
}
public override Stream GetResponseStream()
{
return new MemoryStream(_data);
}
}
public WebRequest Create(Uri uri)
{
return new DataWebRequest(uri);
}
} }

View file

@ -4,45 +4,52 @@ using NLog;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Exception; using Flow.Launcher.Infrastructure.Exception;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
public static class ErrorReporting
{ {
public static class ErrorReporting private static void Report(Exception e)
{ {
private static void Report(Exception e) var logger = LogManager.GetLogger("UnHandledException");
{ logger.Fatal(ExceptionFormatter.FormatExcpetion(e));
var logger = LogManager.GetLogger("UnHandledException"); var reportWindow = new ReportWindow(e);
logger.Fatal(ExceptionFormatter.FormatExcpetion(e)); reportWindow.Show();
var reportWindow = new ReportWindow(e); }
reportWindow.Show();
}
public static void UnhandledExceptionHandle(object sender, UnhandledExceptionEventArgs e) public static void UnhandledExceptionHandle(object sender, UnhandledExceptionEventArgs e)
{ {
//handle non-ui thread exceptions //handle non-ui thread exceptions
Report((Exception)e.ExceptionObject); Report((Exception)e.ExceptionObject);
} }
public static void DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) public static void DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{ {
//handle ui thread exceptions //handle ui thread exceptions
Report(e.Exception); Report(e.Exception);
//prevent application exist, so the user can copy prompted error info //prevent application exist, so the user can copy prompted error info
e.Handled = true; e.Handled = true;
} }
public static string RuntimeInfo()
{
var info = $"\nFlow Launcher version: {Constant.Version}" +
$"\nOS Version: {ExceptionFormatter.GetWindowsFullVersionFromRegistry()}" +
$"\nIntPtr Length: {IntPtr.Size}" +
$"\nx64: {Environment.Is64BitOperatingSystem}";
return info;
}
public static string DependenciesInfo() public static string RuntimeInfo()
{ {
var info = $"\nPython Path: {Constant.PythonPath}\nNode Path: {Constant.NodePath}"; var info =
return info; $"""
}
Flow Launcher version: {Constant.Version}
OS Version: {ExceptionFormatter.GetWindowsFullVersionFromRegistry()}
IntPtr Length: {IntPtr.Size}
x64: {Environment.Is64BitOperatingSystem}
""";
return info;
}
public static string DependenciesInfo()
{
var info = $"""
Python Path: {Constant.PythonPath}
Node Path: {Constant.NodePath}
""";
return info;
} }
} }

View file

@ -7,97 +7,96 @@ using Flow.Launcher.Core.Resource;
using System.Windows; using System.Windows;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
internal static class HotKeyMapper
{ {
internal static class HotKeyMapper private static Settings _settings;
private static MainViewModel _mainViewModel;
internal static void Initialize(MainViewModel mainVM)
{ {
private static Settings settings; _mainViewModel = mainVM;
private static MainViewModel mainViewModel; _settings = _mainViewModel.Settings;
internal static void Initialize(MainViewModel mainVM) SetHotkey(_settings.Hotkey, OnToggleHotkey);
LoadCustomPluginHotkey();
}
internal static void OnToggleHotkey(object sender, HotkeyEventArgs args)
{
if (!_mainViewModel.ShouldIgnoreHotkeys())
_mainViewModel.ToggleFlowLauncher();
}
private static void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
}
internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
{
string hotkeyStr = hotkey.ToString();
try
{ {
mainViewModel = mainVM; HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
settings = mainViewModel.Settings;
SetHotkey(settings.Hotkey, OnToggleHotkey);
LoadCustomPluginHotkey();
} }
catch (Exception)
internal static void OnToggleHotkey(object sender, HotkeyEventArgs args)
{ {
if (!mainViewModel.ShouldIgnoreHotkeys()) string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
mainViewModel.ToggleFlowLauncher(); string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
} MessageBox.Show(errorMsg,errorMsgTitle);
private static void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
}
internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
{
string hotkeyStr = hotkey.ToString();
try
{
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
}
catch (Exception)
{
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
MessageBox.Show(errorMsg,errorMsgTitle);
}
}
internal static void RemoveHotkey(string hotkeyStr)
{
if (!string.IsNullOrEmpty(hotkeyStr))
{
HotkeyManager.Current.Remove(hotkeyStr);
}
}
internal static void LoadCustomPluginHotkey()
{
if (settings.CustomPluginHotkeys == null)
return;
foreach (CustomPluginHotkey hotkey in settings.CustomPluginHotkeys)
{
SetCustomQueryHotkey(hotkey);
}
}
internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey)
{
SetHotkey(hotkey.Hotkey, (s, e) =>
{
if (mainViewModel.ShouldIgnoreHotkeys())
return;
mainViewModel.Show();
mainViewModel.ChangeQueryText(hotkey.ActionKeyword, true);
});
}
internal static bool CheckAvailability(HotkeyModel currentHotkey)
{
try
{
HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", currentHotkey.CharKey, currentHotkey.ModifierKeys, (sender, e) => { });
return true;
}
catch
{
}
finally
{
HotkeyManager.Current.Remove("HotkeyAvailabilityTest");
}
return false;
} }
} }
internal static void RemoveHotkey(string hotkeyStr)
{
if (!string.IsNullOrEmpty(hotkeyStr))
{
HotkeyManager.Current.Remove(hotkeyStr);
}
}
internal static void LoadCustomPluginHotkey()
{
if (_settings.CustomPluginHotkeys == null)
return;
foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys)
{
SetCustomQueryHotkey(hotkey);
}
}
internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey)
{
SetHotkey(hotkey.Hotkey, (s, e) =>
{
if (_mainViewModel.ShouldIgnoreHotkeys())
return;
_mainViewModel.Show();
_mainViewModel.ChangeQueryText(hotkey.ActionKeyword, true);
});
}
internal static bool CheckAvailability(HotkeyModel currentHotkey)
{
try
{
HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", currentHotkey.CharKey, currentHotkey.ModifierKeys, (sender, e) => { });
return true;
}
catch
{
}
finally
{
HotkeyManager.Current.Remove("HotkeyAvailabilityTest");
}
return false;
}
} }

View file

@ -2,27 +2,26 @@
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
{
public static class SingletonWindowOpener
{
public static T Open<T>(params object[] args) where T : Window
{
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetType() == typeof(T))
?? (T)Activator.CreateInstance(typeof(T), args);
// Fix UI bug
// Add `window.WindowState = WindowState.Normal`
// If only use `window.Show()`, Settings-window doesn't show when minimized in taskbar
// Not sure why this works tho
// Probably because, when `.Show()` fails, `window.WindowState == Minimized` (not `Normal`)
// https://stackoverflow.com/a/59719760/4230390
window.WindowState = WindowState.Normal;
window.Show();
window.Focus();
return (T)window; public static class SingletonWindowOpener
} {
public static T Open<T>(params object[] args) where T : Window
{
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetType() == typeof(T))
?? (T)Activator.CreateInstance(typeof(T), args);
// Fix UI bug
// Add `window.WindowState = WindowState.Normal`
// If only use `window.Show()`, Settings-window doesn't show when minimized in taskbar
// Not sure why this works tho
// Probably because, when `.Show()` fails, `window.WindowState == Minimized` (not `Normal`)
// https://stackoverflow.com/a/59719760/4230390
window.WindowState = WindowState.Normal;
window.Show();
window.Focus();
return (T)window;
} }
} }

View file

@ -1,24 +1,23 @@
using System; using System;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
{
public static class SyntaxSugars
{
public static TResult CallOrRescueDefault<TResult>(Func<TResult> callback)
{
return CallOrRescueDefault(callback, default(TResult));
}
public static TResult CallOrRescueDefault<TResult>(Func<TResult> callback, TResult def) public static class SyntaxSugars
{
public static TResult CallOrRescueDefault<TResult>(Func<TResult> callback)
{
return CallOrRescueDefault(callback, default(TResult));
}
public static TResult CallOrRescueDefault<TResult>(Func<TResult> callback, TResult def)
{
try
{ {
try return callback();
{ }
return callback(); catch
} {
catch return def;
{
return def;
}
} }
} }
} }

View file

@ -5,44 +5,43 @@ using System.Text;
using System.Windows.Media; using System.Windows.Media;
using Microsoft.Win32; using Microsoft.Win32;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
public static class WallpaperPathRetrieval
{ {
public static class WallpaperPathRetrieval [DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern Int32 SystemParametersInfo(UInt32 action,
Int32 uParam, StringBuilder vParam, UInt32 winIni);
private static readonly UInt32 SPI_GETDESKWALLPAPER = 0x73;
private static int MAX_PATH = 260;
public static string GetWallpaperPath()
{ {
[DllImport("user32.dll", CharSet = CharSet.Unicode)] var wallpaper = new StringBuilder(MAX_PATH);
private static extern Int32 SystemParametersInfo(UInt32 action, SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallpaper, 0);
Int32 uParam, StringBuilder vParam, UInt32 winIni);
private static readonly UInt32 SPI_GETDESKWALLPAPER = 0x73;
private static int MAX_PATH = 260;
public static string GetWallpaperPath() var str = wallpaper.ToString();
if (string.IsNullOrEmpty(str))
return null;
return str;
}
public static Color GetWallpaperColor()
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true);
var result = key?.GetValue("Background", null);
if (result is string strResult)
{ {
var wallpaper = new StringBuilder(MAX_PATH); try
SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallpaper, 0); {
var parts = strResult.Trim().Split(new[] {' '}, 3).Select(byte.Parse).ToList();
var str = wallpaper.ToString(); return Color.FromRgb(parts[0], parts[1], parts[2]);
if (string.IsNullOrEmpty(str)) }
return null; catch
return str;
}
public static Color GetWallpaperColor()
{
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Colors", true);
var result = key.GetValue(@"Background", null);
if (result != null && result is string)
{ {
try
{
var parts = result.ToString().Trim().Split(new[] {' '}, 3).Select(byte.Parse).ToList();
return Color.FromRgb(parts[0], parts[1], parts[2]);
}
catch
{
}
} }
return Colors.Transparent;
} }
return Colors.Transparent;
} }
} }

View file

@ -8,156 +8,152 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using Point = System.Windows.Point; using Point = System.Windows.Point;
namespace Flow.Launcher.Helper namespace Flow.Launcher.Helper;
public class WindowsInteropHelper
{ {
public class WindowsInteropHelper private const int GWL_STYLE = -16; //WPF's Message code for Title Bar's Style
private const int WS_SYSMENU = 0x80000; //WPF's Message code for System Menu
private static IntPtr _hwnd_shell;
private static IntPtr _hwnd_desktop;
//Accessors for shell and desktop handlers
//Will set the variables once and then will return them
private static IntPtr HWND_SHELL
{ {
private const int GWL_STYLE = -16; //WPF's Message code for Title Bar's Style get
private const int WS_SYSMENU = 0x80000; //WPF's Message code for System Menu
private static IntPtr _hwnd_shell;
private static IntPtr _hwnd_desktop;
//Accessors for shell and desktop handlers
//Will set the variables once and then will return them
private static IntPtr HWND_SHELL
{ {
get return _hwnd_shell != IntPtr.Zero ? _hwnd_shell : _hwnd_shell = GetShellWindow();
{
return _hwnd_shell != IntPtr.Zero ? _hwnd_shell : _hwnd_shell = GetShellWindow();
}
} }
private static IntPtr HWND_DESKTOP }
private static IntPtr HWND_DESKTOP
{
get
{ {
get return _hwnd_desktop != IntPtr.Zero ? _hwnd_desktop : _hwnd_desktop = GetDesktopWindow();
{
return _hwnd_desktop != IntPtr.Zero ? _hwnd_desktop : _hwnd_desktop = GetDesktopWindow();
}
} }
}
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
internal static extern int GetWindowLong(IntPtr hWnd, int nIndex); internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")] [DllImport("user32.dll")]
internal static extern IntPtr GetForegroundWindow(); internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")] [DllImport("user32.dll")]
internal static extern IntPtr GetDesktopWindow(); internal static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] [DllImport("user32.dll")]
internal static extern IntPtr GetShellWindow(); internal static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll")]
internal static extern int GetWindowRect(IntPtr hwnd, out RECT rc); internal static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] [DllImport("user32.dll", SetLastError = true)]
internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); internal static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
[DllImport("user32.DLL")] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.DLL")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass"; const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass";
const string WINDOW_CLASS_WINTAB = "Flip3D"; const string WINDOW_CLASS_WINTAB = "Flip3D";
const string WINDOW_CLASS_PROGMAN = "Progman"; const string WINDOW_CLASS_PROGMAN = "Progman";
const string WINDOW_CLASS_WORKERW = "WorkerW"; const string WINDOW_CLASS_WORKERW = "WorkerW";
public static bool IsWindowFullscreen() public static bool IsWindowFullscreen()
{
//get current active window
IntPtr hWnd = GetForegroundWindow();
if (hWnd.Equals(IntPtr.Zero))
{ {
//get current active window
IntPtr hWnd = GetForegroundWindow();
if (!hWnd.Equals(IntPtr.Zero))
{
//if current active window is NOT desktop or shell
if (!(hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL)))
{
StringBuilder sb = new StringBuilder(256);
GetClassName(hWnd, sb, sb.Capacity);
string windowClass = sb.ToString();
//for Win+Tab (Flip3D)
if (windowClass == WINDOW_CLASS_WINTAB)
{
return false;
}
RECT appBounds;
GetWindowRect(hWnd, out appBounds);
//for console (ConsoleWindowClass), we have to check for negative dimensions
if (windowClass == WINDOW_CLASS_CONSOLE)
{
return appBounds.Top < 0 && appBounds.Bottom < 0;
}
//for desktop (Progman or WorkerW, depends on the system), we have to check
if (windowClass == WINDOW_CLASS_PROGMAN || windowClass == WINDOW_CLASS_WORKERW)
{
IntPtr hWndDesktop = FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null);
hWndDesktop = FindWindowEx(hWndDesktop, IntPtr.Zero, "SysListView32", "FolderView");
if (!hWndDesktop.Equals(IntPtr.Zero))
{
return false;
}
}
Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds;
if ((appBounds.Bottom - appBounds.Top) == screenBounds.Height && (appBounds.Right - appBounds.Left) == screenBounds.Width)
{
return true;
}
}
}
return false; return false;
} }
/// <summary> //if current active window is desktop or shell, exit early
/// disable windows toolbar's control box if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL))
/// this will also disable system menu with Alt+Space hotkey
/// </summary>
public static void DisableControlBox(Window win)
{ {
var hwnd = new WindowInteropHelper(win).Handle; return false;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
} }
/// <summary> StringBuilder sb = new StringBuilder(256);
/// Transforms pixels to Device Independent Pixels used by WPF GetClassName(hWnd, sb, sb.Capacity);
/// </summary> string windowClass = sb.ToString();
/// <param name="visual">current window, required to get presentation source</param>
/// <param name="unitX">horizontal position in pixels</param> //for Win+Tab (Flip3D)
/// <param name="unitY">vertical position in pixels</param> if (windowClass == WINDOW_CLASS_WINTAB)
/// <returns>point containing device independent pixels</returns>
public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY)
{ {
Matrix matrix; return false;
var source = PresentationSource.FromVisual(visual); }
if (source != null)
RECT appBounds;
GetWindowRect(hWnd, out appBounds);
//for console (ConsoleWindowClass), we have to check for negative dimensions
if (windowClass == WINDOW_CLASS_CONSOLE)
{
return appBounds.Top < 0 && appBounds.Bottom < 0;
}
//for desktop (Progman or WorkerW, depends on the system), we have to check
if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW)
{
IntPtr hWndDesktop = FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null);
hWndDesktop = FindWindowEx(hWndDesktop, IntPtr.Zero, "SysListView32", "FolderView");
if (!hWndDesktop.Equals(IntPtr.Zero))
{ {
matrix = source.CompositionTarget.TransformFromDevice; return false;
} }
else
{
using (var src = new HwndSource(new HwndSourceParameters()))
{
matrix = src.CompositionTarget.TransformFromDevice;
}
}
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
} }
Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds;
return (appBounds.Bottom - appBounds.Top) == screenBounds.Height && (appBounds.Right - appBounds.Left) == screenBounds.Width;
}
[StructLayout(LayoutKind.Sequential)] /// <summary>
public struct RECT /// disable windows toolbar's control box
/// this will also disable system menu with Alt+Space hotkey
/// </summary>
public static void DisableControlBox(Window win)
{
var hwnd = new WindowInteropHelper(win).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
}
/// <summary>
/// Transforms pixels to Device Independent Pixels used by WPF
/// </summary>
/// <param name="visual">current window, required to get presentation source</param>
/// <param name="unitX">horizontal position in pixels</param>
/// <param name="unitY">vertical position in pixels</param>
/// <returns>point containing device independent pixels</returns>
public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY)
{
Matrix matrix;
var source = PresentationSource.FromVisual(visual);
if (source is not null)
{ {
public int Left; matrix = source.CompositionTarget.TransformFromDevice;
public int Top;
public int Right;
public int Bottom;
} }
else
{
using var src = new HwndSource(new HwndSourceParameters());
matrix = src.CompositionTarget.TransformFromDevice;
}
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
} }
} }

View file

@ -3,54 +3,75 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Height="24"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid> <Button
<Grid.ColumnDefinitions> Width="Auto"
<ColumnDefinition Width="200" /> FontSize="13"
</Grid.ColumnDefinitions> FontWeight="Bold"
<Popup Foreground="{DynamicResource Color01B}"
x:Name="popup" Click="GetNewHotkey">
AllowDrop="True" <Button.Template>
AllowsTransparency="True" <ControlTemplate TargetType="Button">
IsOpen="{Binding IsKeyboardFocused, ElementName=tbHotkey, Mode=OneWay}" <Border
Placement="Top" x:Name="ButtonBorder"
PlacementTarget="{Binding ElementName=tbHotkey}" Padding="5,0,5,0"
PopupAnimation="Fade" Background="{DynamicResource ButtonBackgroundColor}"
StaysOpen="True" BorderBrush="{DynamicResource ButtonInsideBorder}"
VerticalOffset="-5"> BorderThickness="1"
<Border CornerRadius="5">
Width="140" <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
Height="30" </Border>
Background="{DynamicResource Color01B}" <ControlTemplate.Triggers>
BorderBrush="{DynamicResource Color21B}" <MultiTrigger>
BorderThickness="1" <MultiTrigger.Conditions>
CornerRadius="4"> <Condition Property="IsMouseOver" Value="True" />
<TextBlock <Condition Property="IsPressed" Value="True" />
x:Name="tbMsg" </MultiTrigger.Conditions>
Margin="0,0,0,0" <Setter TargetName="ButtonBorder" Property="Background"
HorizontalAlignment="Center" Value="{DynamicResource ButtonMousePressed}" />
VerticalAlignment="Center" <Setter TargetName="ButtonBorder" Property="BorderBrush"
FontSize="13" Value="{DynamicResource ButtonMousePressedInsideBorder}" />
FontWeight="SemiBold" </MultiTrigger>
Foreground="{DynamicResource Color05B}" <MultiTrigger>
Text="{DynamicResource flowlauncherPressHotkey}" <MultiTrigger.Conditions>
Visibility="Visible" /> <Condition Property="IsMouseOver" Value="True" />
</Border> </MultiTrigger.Conditions>
</Popup> <Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ButtonMouseOver}" />
<TextBox </MultiTrigger>
x:Name="tbHotkey" <MultiTrigger>
Margin="0,0,18,0" <MultiTrigger.Conditions>
VerticalContentAlignment="Center" <Condition Property="IsPressed" Value="True" />
input:InputMethod.IsInputMethodEnabled="False" </MultiTrigger.Conditions>
GotFocus="tbHotkey_GotFocus" <Setter TargetName="ButtonBorder" Property="Background"
LostFocus="tbHotkey_LostFocus" Value="{DynamicResource ButtonMousePressed}" />
PreviewKeyDown="TbHotkey_OnPreviewKeyDown" <Setter TargetName="ButtonBorder" Property="BorderBrush"
TabIndex="100" /> Value="{DynamicResource CustomContextHover}" />
</Grid> </MultiTrigger>
</UserControl> </ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
<Button.Content>
<ItemsControl x:Name="HotkeyList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Margin="2,5,2,5"
Padding="10,5,10,5"
Background="{DynamicResource AccentButtonBackground}"
BorderThickness="1"
CornerRadius="5">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Button.Content>
</Button>
</UserControl>

View file

@ -1,141 +1,204 @@
using System; #nullable enable
using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper; using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using System.Threading;
namespace Flow.Launcher namespace Flow.Launcher
{ {
public partial class HotkeyControl : UserControl public partial class HotkeyControl
{ {
public HotkeyModel CurrentHotkey { get; private set; } public IHotkeySettings HotkeySettings {
public bool CurrentHotkeyAvailable { get; private set; } get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); }
set { SetValue(HotkeySettingsProperty, value); }
}
public event EventHandler HotkeyChanged; public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register(
nameof(HotkeySettings),
typeof(IHotkeySettings),
typeof(HotkeyControl),
new PropertyMetadata()
);
public string WindowTitle {
get { return (string)GetValue(WindowTitleProperty); }
set { SetValue(WindowTitleProperty, value); }
}
public static readonly DependencyProperty WindowTitleProperty = DependencyProperty.Register(
nameof(WindowTitle),
typeof(string),
typeof(HotkeyControl),
new PropertyMetadata(string.Empty)
);
/// <summary> /// <summary>
/// Designed for Preview Hotkey and KeyGesture. /// Designed for Preview Hotkey and KeyGesture.
/// </summary> /// </summary>
public bool ValidateKeyGesture { get; set; } = false; public static readonly DependencyProperty ValidateKeyGestureProperty = DependencyProperty.Register(
nameof(ValidateKeyGesture),
typeof(bool),
typeof(HotkeyControl),
new PropertyMetadata(default(bool))
);
protected virtual void OnHotkeyChanged() => HotkeyChanged?.Invoke(this, EventArgs.Empty); public bool ValidateKeyGesture
public HotkeyControl()
{ {
InitializeComponent(); get { return (bool)GetValue(ValidateKeyGestureProperty); }
set { SetValue(ValidateKeyGestureProperty, value); }
} }
private CancellationTokenSource hotkeyUpdateSource; public static readonly DependencyProperty DefaultHotkeyProperty = DependencyProperty.Register(
nameof(DefaultHotkey),
typeof(string),
typeof(HotkeyControl),
new PropertyMetadata(default(string))
);
private void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e) public string DefaultHotkey
{ {
hotkeyUpdateSource?.Cancel(); get { return (string)GetValue(DefaultHotkeyProperty); }
hotkeyUpdateSource?.Dispose(); set { SetValue(DefaultHotkeyProperty, value); }
hotkeyUpdateSource = new(); }
var token = hotkeyUpdateSource.Token;
e.Handled = true;
//when alt is pressed, the real key should be e.SystemKey private static void OnHotkeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
Key key = e.Key == Key.System ? e.SystemKey : e.Key; {
if (d is not HotkeyControl hotkeyControl)
SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers();
var hotkeyModel = new HotkeyModel(
specialKeyState.AltPressed,
specialKeyState.ShiftPressed,
specialKeyState.WinPressed,
specialKeyState.CtrlPressed,
key);
if (hotkeyModel.Equals(CurrentHotkey))
{ {
return; return;
} }
_ = Dispatcher.InvokeAsync(async () => hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey));
{ hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey);
await Task.Delay(500, token);
if (!token.IsCancellationRequested)
await SetHotkeyAsync(hotkeyModel);
});
} }
public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true)
{
tbHotkey.Text = keyModel.ToString();
tbHotkey.Select(tbHotkey.Text.Length, 0);
public static readonly DependencyProperty ChangeHotkeyProperty = DependencyProperty.Register(
nameof(ChangeHotkey),
typeof(ICommand),
typeof(HotkeyControl),
new PropertyMetadata(default(ICommand))
);
public ICommand? ChangeHotkey
{
get { return (ICommand)GetValue(ChangeHotkeyProperty); }
set { SetValue(ChangeHotkeyProperty, value); }
}
public static readonly DependencyProperty HotkeyProperty = DependencyProperty.Register(
nameof(Hotkey),
typeof(string),
typeof(HotkeyControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHotkeyChanged)
);
public string Hotkey
{
get { return (string)GetValue(HotkeyProperty); }
set { SetValue(HotkeyProperty, value); }
}
public HotkeyControl()
{
InitializeComponent();
HotkeyList.ItemsSource = KeysToDisplay;
SetKeysToDisplay(CurrentHotkey);
}
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
public string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none");
public ObservableCollection<string> KeysToDisplay { get; set; } = new();
public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None);
public void GetNewHotkey(object sender, RoutedEventArgs e)
{
OpenHotkeyDialog();
}
private async Task OpenHotkeyDialog()
{
if (!string.IsNullOrEmpty(Hotkey))
{
HotKeyMapper.RemoveHotkey(Hotkey);
}
var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle);
await dialog.ShowAsync();
switch (dialog.ResultType)
{
case HotkeyControlDialog.EResultType.Cancel:
SetHotkey(Hotkey);
return;
case HotkeyControlDialog.EResultType.Save:
SetHotkey(dialog.ResultValue);
break;
case HotkeyControlDialog.EResultType.Delete:
Delete();
break;
}
}
private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true)
{
if (triggerValidate) if (triggerValidate)
{ {
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture); bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
CurrentHotkeyAvailable = hotkeyAvailable;
SetMessage(hotkeyAvailable);
OnHotkeyChanged();
var token = hotkeyUpdateSource.Token; if (!hotkeyAvailable)
await Task.Delay(500, token);
if (token.IsCancellationRequested)
return;
if (CurrentHotkeyAvailable)
{ {
CurrentHotkey = keyModel; return;
// To trigger LostFocus
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
Keyboard.ClearFocus();
} }
Hotkey = keyModel.ToString();
SetKeysToDisplay(CurrentHotkey);
ChangeHotkey?.Execute(keyModel);
} }
else else
{ {
CurrentHotkey = keyModel; Hotkey = keyModel.ToString();
ChangeHotkey?.Execute(keyModel);
} }
} }
public Task SetHotkeyAsync(string keyStr, bool triggerValidate = true) public void Delete()
{ {
return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate); if (!string.IsNullOrEmpty(Hotkey))
HotKeyMapper.RemoveHotkey(Hotkey);
Hotkey = "";
SetKeysToDisplay(new HotkeyModel(false, false, false, false, Key.None));
} }
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); private void SetKeysToDisplay(HotkeyModel? hotkey)
public new bool IsFocused => tbHotkey.IsFocused;
private void tbHotkey_LostFocus(object sender, RoutedEventArgs e)
{ {
tbHotkey.Text = CurrentHotkey?.ToString() ?? ""; KeysToDisplay.Clear();
tbHotkey.Select(tbHotkey.Text.Length, 0);
}
private void tbHotkey_GotFocus(object sender, RoutedEventArgs e) if (hotkey == null || hotkey == default(HotkeyModel))
{
ResetMessage();
}
private void ResetMessage()
{
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey");
tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B");
}
private void SetMessage(bool hotkeyAvailable)
{
if (!hotkeyAvailable)
{ {
tbMsg.Foreground = new SolidColorBrush(Colors.Red); KeysToDisplay.Add(EmptyHotkey);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); return;
} }
else
foreach (var key in hotkey.Value.EnumerateDisplayKeys()!)
{ {
tbMsg.Foreground = new SolidColorBrush(Colors.Green); KeysToDisplay.Add(key);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
} }
tbMsg.Visibility = Visibility.Visible; }
public void SetHotkey(string? keyStr, bool triggerValidate = true)
{
SetHotkey(new HotkeyModel(keyStr), triggerValidate);
} }
} }
} }

View file

@ -0,0 +1,169 @@
<ui:ContentDialog
x:Class="Flow.Launcher.HotkeyControlDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
Background="{DynamicResource PopuBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0,1,0,0"
CornerRadius="8"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
PreviewKeyDown="OnPreviewKeyDown"
Style="{DynamicResource ContentDialog}">
<ui:ContentDialog.Resources>
<Thickness x:Key="ContentDialogPadding">0</Thickness>
<Thickness x:Key="ContentDialogTitleMargin">0</Thickness>
</ui:ContentDialog.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="100" />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<!-- Window title and the keys in the hotkey -->
<Grid Grid.Row="0" Margin="26,12,26,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<TextBlock
Margin="0,0,0,0"
FontSize="20"
FontWeight="SemiBold"
Text="{Binding WindowTitle}"
TextAlignment="Left" />
<TextBlock FontSize="14" Text="{DynamicResource hotkeyRegGuide}" />
</StackPanel>
<Border
Grid.Row="1"
Width="450"
Height="100"
Margin="0,100,0,0"
Padding="26,12,26,0">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl ItemsSource="{Binding KeysToDisplay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
MinWidth="50"
MinHeight="50"
Margin="5,0,5,0"
Padding="8"
Background="{DynamicResource AccentButtonBackground}"
CornerRadius="6">
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Foreground="{DynamicResource Color01B}"
Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</Grid>
<!-- Warning message for when something went wrong with the new hotkey. -->
<Border Grid.Row="1">
<Border
x:Name="Alert"
Width="420"
Padding="0, 10"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Background="{DynamicResource InfoBarWarningBG}"
BorderBrush="{DynamicResource InfoBarBD}"
BorderThickness="1"
CornerRadius="5"
Visibility="Collapsed">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:FontIcon
Grid.Column="0"
Margin="20,0,14,0"
VerticalAlignment="Center"
FontSize="15"
Foreground="{DynamicResource InfoBarWarningIcon}"
Glyph="&#xf167;" />
<TextBlock
Grid.Column="1"
x:Name="tbMsg"
Margin="0,0,0,2"
Padding="0,0,8,0"
HorizontalAlignment="Left"
FontSize="13"
FontWeight="SemiBold"
TextWrapping="Wrap"
Foreground="{DynamicResource Color05B}" />
</Grid>
</Border>
</Border>
<!-- Action buttons at the bottom of the dialog -->
<Border
Grid.Row="2"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0,1,0,0"
CornerRadius="0 0 8 8">
<StackPanel
Margin="10"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="OverwriteBtn"
Height="30"
MinWidth="100"
Margin="0,0,4,0"
Click="Overwrite"
Content="{DynamicResource commonOverwrite}"
Visibility="Collapsed"
Style="{StaticResource AccentButtonStyle}" />
<Button
x:Name="SaveBtn"
Height="30"
MinWidth="100"
Margin="0,0,4,0"
Click="Save"
Content="{DynamicResource commonSave}"
Style="{StaticResource AccentButtonStyle}" />
<Button
Height="30"
MinWidth="100"
Margin="4,0,4,0"
Click="Reset"
Content="{DynamicResource commonReset}" />
<Button
Height="30"
MinWidth="100"
Margin="4,0,4,0"
Click="Delete"
Content="{DynamicResource commonDelete}" />
<Button
Height="30"
MinWidth="100"
Margin="4,0,0,0"
Click="Cancel"
Content="{DynamicResource commonCancel}" />
</StackPanel>
</Border>
</Grid>
</ui:ContentDialog>

View file

@ -0,0 +1,179 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using ModernWpf.Controls;
namespace Flow.Launcher;
#nullable enable
public partial class HotkeyControlDialog : ContentDialog
{
private IHotkeySettings _hotkeySettings;
private Action? _overwriteOtherHotkey;
private string DefaultHotkey { get; }
public string WindowTitle { get; }
public HotkeyModel CurrentHotkey { get; private set; }
public ObservableCollection<string> KeysToDisplay { get; } = new();
public enum EResultType
{
Cancel,
Save,
Delete
}
public EResultType ResultType { get; private set; } = EResultType.Cancel;
public string ResultValue { get; private set; } = string.Empty;
public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none");
public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "")
{
WindowTitle = windowTitle switch
{
"" or null => InternationalizationManager.Instance.GetTranslation("hotkeyRegTitle"),
_ => windowTitle
};
DefaultHotkey = defaultHotkey;
CurrentHotkey = new HotkeyModel(hotkey);
_hotkeySettings = hotkeySettings;
SetKeysToDisplay(CurrentHotkey);
InitializeComponent();
}
private void Reset(object sender, RoutedEventArgs routedEventArgs)
{
SetKeysToDisplay(new HotkeyModel(DefaultHotkey));
}
private void Delete(object sender, RoutedEventArgs routedEventArgs)
{
KeysToDisplay.Clear();
KeysToDisplay.Add(EmptyHotkey);
}
private void Cancel(object sender, RoutedEventArgs routedEventArgs)
{
ResultType = EResultType.Cancel;
Hide();
}
private void Save(object sender, RoutedEventArgs routedEventArgs)
{
if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey)
{
ResultType = EResultType.Delete;
Hide();
return;
}
ResultType = EResultType.Save;
ResultValue = string.Join("+", KeysToDisplay);
Hide();
}
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
//when alt is pressed, the real key should be e.SystemKey
Key key = e.Key == Key.System ? e.SystemKey : e.Key;
SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers();
var hotkeyModel = new HotkeyModel(
specialKeyState.AltPressed,
specialKeyState.ShiftPressed,
specialKeyState.WinPressed,
specialKeyState.CtrlPressed,
key);
CurrentHotkey = hotkeyModel;
SetKeysToDisplay(CurrentHotkey);
}
private void SetKeysToDisplay(HotkeyModel? hotkey)
{
_overwriteOtherHotkey = null;
KeysToDisplay.Clear();
if (hotkey == null || hotkey == default(HotkeyModel))
{
KeysToDisplay.Add(EmptyHotkey);
return;
}
foreach (var key in hotkey.Value.EnumerateDisplayKeys()!)
{
KeysToDisplay.Add(key);
}
if (tbMsg == null)
return;
if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData)
{
var description = string.Format(
InternationalizationManager.Instance.GetTranslation(registeredHotkeyData.DescriptionResourceKey),
registeredHotkeyData.DescriptionFormatVariables
);
Alert.Visibility = Visibility.Visible;
if (registeredHotkeyData.RemoveHotkey is not null)
{
tbMsg.Text = string.Format(
InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableEditable"),
description
);
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Collapsed;
OverwriteBtn.IsEnabled = true;
OverwriteBtn.Visibility = Visibility.Visible;
_overwriteOtherHotkey = registeredHotkeyData.RemoveHotkey;
}
else
{
tbMsg.Text = string.Format(
InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableUneditable"),
description
);
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible;
OverwriteBtn.IsEnabled = false;
OverwriteBtn.Visibility = Visibility.Collapsed;
}
return;
}
OverwriteBtn.IsEnabled = false;
OverwriteBtn.Visibility = Visibility.Collapsed;
if (!CheckHotkeyAvailability(hotkey.Value, true))
{
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible;
}
else
{
Alert.Visibility = Visibility.Collapsed;
SaveBtn.IsEnabled = true;
SaveBtn.Visibility = Visibility.Visible;
}
}
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
private void Overwrite(object sender, RoutedEventArgs e)
{
_overwriteOtherHotkey?.Invoke();
Save(sender, e);
}
}

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Dark</system:String> <system:String x:Key="ColorSchemeDark">Dark</system:String>
<system:String x:Key="SoundEffect">Sound Effect</system:String> <system:String x:Key="SoundEffect">Sound Effect</system:String>
<system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String> <system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Use Animation in UI</system:String> <system:String x:Key="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Tmavý</system:String> <system:String x:Key="ColorSchemeDark">Tmavý</system:String>
<system:String x:Key="SoundEffect">Zvukový efekt</system:String> <system:String x:Key="SoundEffect">Zvukový efekt</system:String>
<system:String x:Key="SoundEffectTip">Přehrát krátký zvuk při otevření okna vyhledávání</system:String> <system:String x:Key="SoundEffectTip">Přehrát krátký zvuk při otevření okna vyhledávání</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animace</system:String> <system:String x:Key="Animation">Animace</system:String>
<system:String x:Key="AnimationTip">Použít animaci v UI</system:String> <system:String x:Key="AnimationTip">Použít animaci v UI</system:String>
<system:String x:Key="AnimationSpeed">Rychlost animace</system:String> <system:String x:Key="AnimationSpeed">Rychlost animace</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Dark</system:String> <system:String x:Key="ColorSchemeDark">Dark</system:String>
<system:String x:Key="SoundEffect">Sound Effect</system:String> <system:String x:Key="SoundEffect">Sound Effect</system:String>
<system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String> <system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Use Animation in UI</system:String> <system:String x:Key="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Dunkel</system:String> <system:String x:Key="ColorSchemeDark">Dunkel</system:String>
<system:String x:Key="SoundEffect">Soundeffekt</system:String> <system:String x:Key="SoundEffect">Soundeffekt</system:String>
<system:String x:Key="SoundEffectTip">Ton abspielen, wenn das Suchfenster geöffnet wird</system:String> <system:String x:Key="SoundEffectTip">Ton abspielen, wenn das Suchfenster geöffnet wird</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Animationen in der Oberfläche verwenden</system:String> <system:String x:Key="AnimationTip">Animationen in der Oberfläche verwenden</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -156,6 +156,8 @@
<system:String x:Key="ColorSchemeDark">Dark</system:String> <system:String x:Key="ColorSchemeDark">Dark</system:String>
<system:String x:Key="SoundEffect">Sound Effect</system:String> <system:String x:Key="SoundEffect">Sound Effect</system:String>
<system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String> <system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Use Animation in UI</system:String> <system:String x:Key="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>
@ -170,14 +172,35 @@
<!-- Setting Hotkey --> <!-- Setting Hotkey -->
<system:String x:Key="hotkey">Hotkey</system:String> <system:String x:Key="hotkey">Hotkey</system:String>
<system:String x:Key="hotkeys">Hotkeys</system:String> <system:String x:Key="hotkeys">Hotkeys</system:String>
<system:String x:Key="flowlauncherHotkey">Flow Launcher Hotkey</system:String> <system:String x:Key="flowlauncherHotkey">Open Flow Launcher</system:String>
<system:String x:Key="flowlauncherHotkeyToolTip">Enter shortcut to show/hide Flow Launcher.</system:String> <system:String x:Key="flowlauncherHotkeyToolTip">Enter shortcut to show/hide Flow Launcher.</system:String>
<system:String x:Key="previewHotkey">Preview Hotkey</system:String> <system:String x:Key="previewHotkey">Toggle Preview</system:String>
<system:String x:Key="previewHotkeyToolTip">Enter shortcut to show/hide preview in search window.</system:String> <system:String x:Key="previewHotkeyToolTip">Enter shortcut to show/hide preview in search window.</system:String>
<system:String x:Key="hotkeyPresets">Hotkey Presets</system:String>
<system:String x:Key="hotkeyPresetsToolTip">List of currently registered hotkeys</system:String>
<system:String x:Key="openResultModifiers">Open Result Modifier Key</system:String> <system:String x:Key="openResultModifiers">Open Result Modifier Key</system:String>
<system:String x:Key="openResultModifiersToolTip">Select a modifier key to open selected result via keyboard.</system:String> <system:String x:Key="openResultModifiersToolTip">Select a modifier key to open selected result via keyboard.</system:String>
<system:String x:Key="showOpenResultHotkey">Show Hotkey</system:String> <system:String x:Key="showOpenResultHotkey">Show Hotkey</system:String>
<system:String x:Key="showOpenResultHotkeyToolTip">Show result selection hotkey with results.</system:String> <system:String x:Key="showOpenResultHotkeyToolTip">Show result selection hotkey with results.</system:String>
<system:String x:Key="autoCompleteHotkey">Auto Complete</system:String>
<system:String x:Key="autoCompleteHotkeyToolTip">Runs autocomplete for the selected items.</system:String>
<system:String x:Key="SelectNextItemHotkey">Select Next Item</system:String>
<system:String x:Key="SelectPrevItemHotkey">Select Previous Item</system:String>
<system:String x:Key="SelectNextPageHotkey">Next Page</system:String>
<system:String x:Key="SelectPrevPageHotkey">Previous Page</system:String>
<system:String x:Key="OpenContextMenuHotkey">Open Context Menu</system:String>
<system:String x:Key="SettingWindowHotkey">Open Setting Window</system:String>
<system:String x:Key="CopyFilePathHotkey">Copy File Path</system:String>
<system:String x:Key="ToggleGameModeHotkey">Toggle Game Mode</system:String>
<system:String x:Key="ToggleHistoryHotkey">Toggle History</system:String>
<system:String x:Key="OpenContainFolderHotkey">Open Containing Folder</system:String>
<system:String x:Key="RunAsAdminHotkey">Run As Admin</system:String>
<system:String x:Key="RequeryHotkey">Refresh Search Results</system:String>
<system:String x:Key="ReloadPluginHotkey">Reload Plugins Data</system:String>
<system:String x:Key="QuickWidthHotkey">Quick Adjust Window Width</system:String>
<system:String x:Key="QuickHeightHotkey">Quick Adjust Window Height</system:String>
<system:String x:Key="ReloadPluginHotkeyToolTip">Use when require plugins to reload and update their existing data.</system:String>
<system:String x:Key="AdditionalHotkeyToolTip">You can add one more hotkey for this function.</system:String>
<system:String x:Key="customQueryHotkey">Custom Query Hotkeys</system:String> <system:String x:Key="customQueryHotkey">Custom Query Hotkeys</system:String>
<system:String x:Key="customQueryShortcut">Custom Query Shortcuts</system:String> <system:String x:Key="customQueryShortcut">Custom Query Shortcuts</system:String>
<system:String x:Key="builtinShortcuts">Built-in Shortcuts</system:String> <system:String x:Key="builtinShortcuts">Built-in Shortcuts</system:String>
@ -188,6 +211,7 @@
<system:String x:Key="delete">Delete</system:String> <system:String x:Key="delete">Delete</system:String>
<system:String x:Key="edit">Edit</system:String> <system:String x:Key="edit">Edit</system:String>
<system:String x:Key="add">Add</system:String> <system:String x:Key="add">Add</system:String>
<system:String x:Key="none">None</system:String>
<system:String x:Key="pleaseSelectAnItem">Please select an item</system:String> <system:String x:Key="pleaseSelectAnItem">Please select an item</system:String>
<system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String> <system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String>
<system:String x:Key="deleteCustomShortcutWarning">Are you sure you want to delete shortcut: {0} with expansion {1}?</system:String> <system:String x:Key="deleteCustomShortcutWarning">Are you sure you want to delete shortcut: {0} with expansion {1}?</system:String>
@ -286,6 +310,11 @@
<system:String x:Key="hotkeyIsNotUnavailable">Hotkey is unavailable, please select a new hotkey</system:String> <system:String x:Key="hotkeyIsNotUnavailable">Hotkey is unavailable, please select a new hotkey</system:String>
<system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String> <system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String>
<system:String x:Key="update">Update</system:String> <system:String x:Key="update">Update</system:String>
<system:String x:Key="hotkeyRegTitle">Binding Hotkey</system:String>
<system:String x:Key="hotkeyUnavailable">Current hotkey is unavailable.</system:String>
<system:String x:Key="hotkeyUnavailableUneditable">This hotkey is reserved for "{0}" and can't be used. Please choose another hotkey.</system:String>
<system:String x:Key="hotkeyUnavailableEditable">This hotkey is already in use by "{0}". If you press "Overwrite", it will be removed from "{0}".</system:String>
<system:String x:Key="hotkeyRegGuide">Press the keys you want to use for this function.</system:String>
<!-- Custom Query Shortcut Dialog --> <!-- Custom Query Shortcut Dialog -->
<system:String x:Key="customeQueryShortcutTitle">Custom Query Shortcut</system:String> <system:String x:Key="customeQueryShortcutTitle">Custom Query Shortcut</system:String>
@ -295,8 +324,12 @@
<system:String x:Key="duplicateShortcut">Shortcut already exists, please enter a new Shortcut or edit the existing one.</system:String> <system:String x:Key="duplicateShortcut">Shortcut already exists, please enter a new Shortcut or edit the existing one.</system:String>
<system:String x:Key="emptyShortcut">Shortcut and/or its expansion is empty.</system:String> <system:String x:Key="emptyShortcut">Shortcut and/or its expansion is empty.</system:String>
<!-- Hotkey Control --> <!-- Common Action -->
<system:String x:Key="hotkeyUnavailable">Hotkey Unavailable</system:String> <system:String x:Key="commonSave">Save</system:String>
<system:String x:Key="commonOverwrite">Overwrite</system:String>
<system:String x:Key="commonCancel">Cancel</system:String>
<system:String x:Key="commonReset">Reset</system:String>
<system:String x:Key="commonDelete">Delete</system:String>
<!-- Crash Reporter --> <!-- Crash Reporter -->
<system:String x:Key="reportWindow_version">Version</system:String> <system:String x:Key="reportWindow_version">Version</system:String>
@ -366,6 +399,12 @@
<system:String x:Key="HotkeyCtrlIDesc">Open Setting Window</system:String> <system:String x:Key="HotkeyCtrlIDesc">Open Setting Window</system:String>
<system:String x:Key="HotkeyF5Desc">Reload Plugin Data</system:String> <system:String x:Key="HotkeyF5Desc">Reload Plugin Data</system:String>
<system:String x:Key="HotkeySelectFirstResult">Select first result</system:String>
<system:String x:Key="HotkeySelectLastResult">Select last result</system:String>
<system:String x:Key="HotkeyRequery">Run current query again</system:String>
<system:String x:Key="HotkeyOpenResult">Open result</system:String>
<system:String x:Key="HotkeyOpenResultN">Open result #{0}</system:String>
<system:String x:Key="RecommendWeather">Weather</system:String> <system:String x:Key="RecommendWeather">Weather</system:String>
<system:String x:Key="RecommendWeatherDesc">Weather in Google Result</system:String> <system:String x:Key="RecommendWeatherDesc">Weather in Google Result</system:String>
<system:String x:Key="RecommendShell">&gt; ping 8.8.8.8</system:String> <system:String x:Key="RecommendShell">&gt; ping 8.8.8.8</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Oscuro</system:String> <system:String x:Key="ColorSchemeDark">Oscuro</system:String>
<system:String x:Key="SoundEffect">Efectos de Sonido</system:String> <system:String x:Key="SoundEffect">Efectos de Sonido</system:String>
<system:String x:Key="SoundEffectTip">Reproducir un sonido al abrir la ventana de búsqueda</system:String> <system:String x:Key="SoundEffectTip">Reproducir un sonido al abrir la ventana de búsqueda</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animación</system:String> <system:String x:Key="Animation">Animación</system:String>
<system:String x:Key="AnimationTip">Usar Animación en la Interfaz</system:String> <system:String x:Key="AnimationTip">Usar Animación en la Interfaz</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -55,7 +55,7 @@
<system:String x:Key="LastQuerySelected">Seleccionar la última consulta</system:String> <system:String x:Key="LastQuerySelected">Seleccionar la última consulta</system:String>
<system:String x:Key="LastQueryEmpty">Limpiar la última consulta</system:String> <system:String x:Key="LastQueryEmpty">Limpiar la última consulta</system:String>
<system:String x:Key="maxShowResults">Número máximo de resultados mostrados</system:String> <system:String x:Key="maxShowResults">Número máximo de resultados mostrados</system:String>
<system:String x:Key="maxShowResultsToolTip">También puede ajustarlo rápidamente usando CTRL+Más y CTRL+Menos.</system:String> <system:String x:Key="maxShowResultsToolTip">También puede ajustarse rápidamente usando Ctrl+Más y Ctrl+Menos.</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignorar atajos de teclado en modo pantalla completa</system:String> <system:String x:Key="ignoreHotkeysOnFullscreen">Ignorar atajos de teclado en modo pantalla completa</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreenToolTip">No permite activar Flow Launcher con aplicaciones a pantalla completa (Recomendado para juegos).</system:String> <system:String x:Key="ignoreHotkeysOnFullscreenToolTip">No permite activar Flow Launcher con aplicaciones a pantalla completa (Recomendado para juegos).</system:String>
<system:String x:Key="defaultFileManager">Administrador de archivos predeterminado</system:String> <system:String x:Key="defaultFileManager">Administrador de archivos predeterminado</system:String>
@ -73,13 +73,13 @@
<system:String x:Key="hideOnStartup">Ocultar Flow Launcher al inicio</system:String> <system:String x:Key="hideOnStartup">Ocultar Flow Launcher al inicio</system:String>
<system:String x:Key="hideNotifyIcon">Ocultar icono en la bandeja del sistema</system:String> <system:String x:Key="hideNotifyIcon">Ocultar icono en la bandeja del sistema</system:String>
<system:String x:Key="hideNotifyIconToolTip">Cuando el icono está oculto en la bandeja del sistema, se puede abrir el menú de configuración haciendo clic con el botón derecho en la ventana de búsqueda.</system:String> <system:String x:Key="hideNotifyIconToolTip">Cuando el icono está oculto en la bandeja del sistema, se puede abrir el menú de configuración haciendo clic con el botón derecho en la ventana de búsqueda.</system:String>
<system:String x:Key="querySearchPrecision">Precisión en la búsqueda de consultas</system:String> <system:String x:Key="querySearchPrecision">Precisión de búsqueda en consultas</system:String>
<system:String x:Key="querySearchPrecisionToolTip">Cambia la puntuación mínima requerida para la coincidencia de los resultados.</system:String> <system:String x:Key="querySearchPrecisionToolTip">Cambia la puntuación mínima requerida para la coincidencia de los resultados.</system:String>
<system:String x:Key="ShouldUsePinyin">Buscar con Pinyin</system:String> <system:String x:Key="ShouldUsePinyin">Buscar con Pinyin</system:String>
<system:String x:Key="ShouldUsePinyinToolTip">Permite utilizar Pinyin para la búsqueda. Pinyin es el sistema estándar de ortografía romanizado para traducir chino.</system:String> <system:String x:Key="ShouldUsePinyinToolTip">Permite utilizar Pinyin para la búsqueda. Pinyin es el sistema estándar de ortografía romanizado para traducir chino.</system:String>
<system:String x:Key="AlwaysPreview">Mostrar siempre vista previa</system:String> <system:String x:Key="AlwaysPreview">Mostrar siempre vista previa</system:String>
<system:String x:Key="AlwaysPreviewToolTip">Muestra siempre el panel de vista previa al iniciar Flow. Pulse {0} para mostrar/ocultar la vista previa.</system:String> <system:String x:Key="AlwaysPreviewToolTip">Muestra siempre el panel de vista previa al iniciar Flow. Pulse {0} para mostrar/ocultar la vista previa.</system:String>
<system:String x:Key="shadowEffectNotAllowed">El efecto de sombra no está permitido mientras el tema actual tenga el efecto de desenfoque activado</system:String> <system:String x:Key="shadowEffectNotAllowed">El efecto de sombra no está permitido si el tema actual tiene activado el efecto de desenfoque</system:String>
<!-- Setting Plugin --> <!-- Setting Plugin -->
<system:String x:Key="searchplugin">Buscar complemento</system:String> <system:String x:Key="searchplugin">Buscar complemento</system:String>
@ -154,8 +154,10 @@
<system:String x:Key="ColorSchemeDark">Oscuro</system:String> <system:String x:Key="ColorSchemeDark">Oscuro</system:String>
<system:String x:Key="SoundEffect">Efecto de sonido</system:String> <system:String x:Key="SoundEffect">Efecto de sonido</system:String>
<system:String x:Key="SoundEffectTip">Reproduce un pequeño sonido cuando se abre el cuadro de búsqueda</system:String> <system:String x:Key="SoundEffectTip">Reproduce un pequeño sonido cuando se abre el cuadro de búsqueda</system:String>
<system:String x:Key="SoundEffectVolume">Volumen del efecto de sonido</system:String>
<system:String x:Key="SoundEffectVolumeTip">Ajusta el volumen del efecto de sonido</system:String>
<system:String x:Key="Animation">Animación</system:String> <system:String x:Key="Animation">Animación</system:String>
<system:String x:Key="AnimationTip">Usar animación en la Interfaz de Usuario</system:String> <system:String x:Key="AnimationTip">Usa animación en la Interfaz de Usuario</system:String>
<system:String x:Key="AnimationSpeed">Velocidad de animación</system:String> <system:String x:Key="AnimationSpeed">Velocidad de animación</system:String>
<system:String x:Key="AnimationSpeedTip">Velocidad de animación de la interfaz de usuario</system:String> <system:String x:Key="AnimationSpeedTip">Velocidad de animación de la interfaz de usuario</system:String>
<system:String x:Key="AnimationSpeedSlow">Lenta</system:String> <system:String x:Key="AnimationSpeedSlow">Lenta</system:String>
@ -170,7 +172,7 @@
<system:String x:Key="hotkeys">Atajos de teclado</system:String> <system:String x:Key="hotkeys">Atajos de teclado</system:String>
<system:String x:Key="flowlauncherHotkey">Atajo de teclado de Flow Launcher</system:String> <system:String x:Key="flowlauncherHotkey">Atajo de teclado de Flow Launcher</system:String>
<system:String x:Key="flowlauncherHotkeyToolTip">Introduzca el atajo de teclado para mostrar/ocultar Flow Launcher.</system:String> <system:String x:Key="flowlauncherHotkeyToolTip">Introduzca el atajo de teclado para mostrar/ocultar Flow Launcher.</system:String>
<system:String x:Key="previewHotkey">Atajo de teclado para vista previa</system:String> <system:String x:Key="previewHotkey">Acceso directo para vista previa</system:String>
<system:String x:Key="previewHotkeyToolTip">Introduzca el acceso directo para mostrar/ocultar la vista previa en la ventana de búsqueda.</system:String> <system:String x:Key="previewHotkeyToolTip">Introduzca el acceso directo para mostrar/ocultar la vista previa en la ventana de búsqueda.</system:String>
<system:String x:Key="openResultModifiers">Tecla modificadora para abrir resultado</system:String> <system:String x:Key="openResultModifiers">Tecla modificadora para abrir resultado</system:String>
<system:String x:Key="openResultModifiersToolTip">Seleccione una tecla modificadora para abrir el resultado seleccionado con el teclado.</system:String> <system:String x:Key="openResultModifiersToolTip">Seleccione una tecla modificadora para abrir el resultado seleccionado con el teclado.</system:String>
@ -194,7 +196,7 @@
<system:String x:Key="queryWindowShadowEffect">Efecto de sombra de la ventana de consultas</system:String> <system:String x:Key="queryWindowShadowEffect">Efecto de sombra de la ventana de consultas</system:String>
<system:String x:Key="shadowEffectCPUUsage">El efecto de sombra hace un uso sustancial de la GPU. No se recomienda para ordenadores de rendimiento limitado.</system:String> <system:String x:Key="shadowEffectCPUUsage">El efecto de sombra hace un uso sustancial de la GPU. No se recomienda para ordenadores de rendimiento limitado.</system:String>
<system:String x:Key="windowWidthSize">Tamaño del ancho de la ventana</system:String> <system:String x:Key="windowWidthSize">Tamaño del ancho de la ventana</system:String>
<system:String x:Key="windowWidthSizeToolTip">También puede ajustarlo rápidamente usando Ctrl+[y Ctrl+].</system:String> <system:String x:Key="windowWidthSizeToolTip">También puede ajustarse rápidamente usando Ctrl+[ y Ctrl+].</system:String>
<system:String x:Key="useGlyphUI">Usar iconos Segoe Fluent</system:String> <system:String x:Key="useGlyphUI">Usar iconos Segoe Fluent</system:String>
<system:String x:Key="useGlyphUIEffect">Usa iconos Segoe Fluent para los resultados de la consulta cuando sean compatibles</system:String> <system:String x:Key="useGlyphUIEffect">Usa iconos Segoe Fluent para los resultados de la consulta cuando sean compatibles</system:String>
<system:String x:Key="flowlauncherPressHotkey">Pulsar Tecla</system:String> <system:String x:Key="flowlauncherPressHotkey">Pulsar Tecla</system:String>
@ -296,7 +298,7 @@ Si añade un prefijo &quot;@&quot; al introducir un acceso directo, éste coinci
<system:String x:Key="emptyShortcut">El acceso directo y/o su expansión están vacíos.</system:String> <system:String x:Key="emptyShortcut">El acceso directo y/o su expansión están vacíos.</system:String>
<!-- Hotkey Control --> <!-- Hotkey Control -->
<system:String x:Key="hotkeyUnavailable">Atajo de teclado no disponible</system:String> <system:String x:Key="hotkeyUnavailable">No disponible</system:String>
<!-- Crash Reporter --> <!-- Crash Reporter -->
<system:String x:Key="reportWindow_version">Versión</system:String> <system:String x:Key="reportWindow_version">Versión</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Sombre</system:String> <system:String x:Key="ColorSchemeDark">Sombre</system:String>
<system:String x:Key="SoundEffect">Effet Sonore</system:String> <system:String x:Key="SoundEffect">Effet Sonore</system:String>
<system:String x:Key="SoundEffectTip">Jouer un petit son lorsque la fenêtre de recherche s'ouvre</system:String> <system:String x:Key="SoundEffectTip">Jouer un petit son lorsque la fenêtre de recherche s'ouvre</system:String>
<system:String x:Key="SoundEffectVolume">Volume de l'effet sonore</system:String>
<system:String x:Key="SoundEffectVolumeTip">Ajuster le volume de l'effet sonore</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Utiliser l'animation dans l'interface</system:String> <system:String x:Key="AnimationTip">Utiliser l'animation dans l'interface</system:String>
<system:String x:Key="AnimationSpeed">Vitesse d'animation</system:String> <system:String x:Key="AnimationSpeed">Vitesse d'animation</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Scuro</system:String> <system:String x:Key="ColorSchemeDark">Scuro</system:String>
<system:String x:Key="SoundEffect">Effetto sonoro</system:String> <system:String x:Key="SoundEffect">Effetto sonoro</system:String>
<system:String x:Key="SoundEffectTip">Riproduce un piccolo suono all'apertura della finestra di ricerca</system:String> <system:String x:Key="SoundEffectTip">Riproduce un piccolo suono all'apertura della finestra di ricerca</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animazione</system:String> <system:String x:Key="Animation">Animazione</system:String>
<system:String x:Key="AnimationTip">Usa l'animazione nell'interfaccia utente</system:String> <system:String x:Key="AnimationTip">Usa l'animazione nell'interfaccia utente</system:String>
<system:String x:Key="AnimationSpeed">Velocità di animazione</system:String> <system:String x:Key="AnimationSpeed">Velocità di animazione</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Dark</system:String> <system:String x:Key="ColorSchemeDark">Dark</system:String>
<system:String x:Key="SoundEffect">Sound Effect</system:String> <system:String x:Key="SoundEffect">Sound Effect</system:String>
<system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String> <system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Use Animation in UI</system:String> <system:String x:Key="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">어둡게</system:String> <system:String x:Key="ColorSchemeDark">어둡게</system:String>
<system:String x:Key="SoundEffect">소리 효과</system:String> <system:String x:Key="SoundEffect">소리 효과</system:String>
<system:String x:Key="SoundEffectTip">검색창을 열 때 작은 소리를 재생합니다.</system:String> <system:String x:Key="SoundEffectTip">검색창을 열 때 작은 소리를 재생합니다.</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">애니메이션</system:String> <system:String x:Key="Animation">애니메이션</system:String>
<system:String x:Key="AnimationTip">일부 UI에 애니메이션을 사용합니다.</system:String> <system:String x:Key="AnimationTip">일부 UI에 애니메이션을 사용합니다.</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Dark</system:String> <system:String x:Key="ColorSchemeDark">Dark</system:String>
<system:String x:Key="SoundEffect">Sound Effect</system:String> <system:String x:Key="SoundEffect">Sound Effect</system:String>
<system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String> <system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Use Animation in UI</system:String> <system:String x:Key="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Donker</system:String> <system:String x:Key="ColorSchemeDark">Donker</system:String>
<system:String x:Key="SoundEffect">Geluidseffect</system:String> <system:String x:Key="SoundEffect">Geluidseffect</system:String>
<system:String x:Key="SoundEffectTip">Een klein geluid afspelen wanneer het zoekvenster wordt geopend</system:String> <system:String x:Key="SoundEffectTip">Een klein geluid afspelen wanneer het zoekvenster wordt geopend</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animatie</system:String> <system:String x:Key="Animation">Animatie</system:String>
<system:String x:Key="AnimationTip">Animatie gebruiken in UI</system:String> <system:String x:Key="AnimationTip">Animatie gebruiken in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Ciemny</system:String> <system:String x:Key="ColorSchemeDark">Ciemny</system:String>
<system:String x:Key="SoundEffect">Efekty dźwiękowe</system:String> <system:String x:Key="SoundEffect">Efekty dźwiękowe</system:String>
<system:String x:Key="SoundEffectTip">Odtwarzaj krótki dźwięk po otwarciu okna wyszukiwania</system:String> <system:String x:Key="SoundEffectTip">Odtwarzaj krótki dźwięk po otwarciu okna wyszukiwania</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animacja</system:String> <system:String x:Key="Animation">Animacja</system:String>
<system:String x:Key="AnimationTip">Użyj animacji w interfejsie użytkownika</system:String> <system:String x:Key="AnimationTip">Użyj animacji w interfejsie użytkownika</system:String>
<system:String x:Key="AnimationSpeed">Szybkość animacji</system:String> <system:String x:Key="AnimationSpeed">Szybkość animacji</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Escuro</system:String> <system:String x:Key="ColorSchemeDark">Escuro</system:String>
<system:String x:Key="SoundEffect">Efeito Sonoro</system:String> <system:String x:Key="SoundEffect">Efeito Sonoro</system:String>
<system:String x:Key="SoundEffectTip">Reproduzir um pequeno som ao abrir a janela de pesquisa</system:String> <system:String x:Key="SoundEffectTip">Reproduzir um pequeno som ao abrir a janela de pesquisa</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animação</system:String> <system:String x:Key="Animation">Animação</system:String>
<system:String x:Key="AnimationTip">Utilizar Animação na Interface</system:String> <system:String x:Key="AnimationTip">Utilizar Animação na Interface</system:String>
<system:String x:Key="AnimationSpeed">Velocidade de Animação</system:String> <system:String x:Key="AnimationSpeed">Velocidade de Animação</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Escuro</system:String> <system:String x:Key="ColorSchemeDark">Escuro</system:String>
<system:String x:Key="SoundEffect">Efeitos sonoros</system:String> <system:String x:Key="SoundEffect">Efeitos sonoros</system:String>
<system:String x:Key="SoundEffectTip">Reproduzir um som ao abrir a janela de pesquisa</system:String> <system:String x:Key="SoundEffectTip">Reproduzir um som ao abrir a janela de pesquisa</system:String>
<system:String x:Key="SoundEffectVolume">Volume dos efeitos sonoros</system:String>
<system:String x:Key="SoundEffectVolumeTip">Ajustar volume dos efeitos sonoros</system:String>
<system:String x:Key="Animation">Animação</system:String> <system:String x:Key="Animation">Animação</system:String>
<system:String x:Key="AnimationTip">Utilizar animações na aplicação</system:String> <system:String x:Key="AnimationTip">Utilizar animações na aplicação</system:String>
<system:String x:Key="AnimationSpeed">Velocidade da animação</system:String> <system:String x:Key="AnimationSpeed">Velocidade da animação</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Тёмная</system:String> <system:String x:Key="ColorSchemeDark">Тёмная</system:String>
<system:String x:Key="SoundEffect">Звуковой эффект</system:String> <system:String x:Key="SoundEffect">Звуковой эффект</system:String>
<system:String x:Key="SoundEffectTip">Воспроизведение небольшого звука при открытии окна поиска</system:String> <system:String x:Key="SoundEffectTip">Воспроизведение небольшого звука при открытии окна поиска</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Анимация</system:String> <system:String x:Key="Animation">Анимация</system:String>
<system:String x:Key="AnimationTip">Использование анимации в меню</system:String> <system:String x:Key="AnimationTip">Использование анимации в меню</system:String>
<system:String x:Key="AnimationSpeed">Скорость анимации</system:String> <system:String x:Key="AnimationSpeed">Скорость анимации</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Tmavá</system:String> <system:String x:Key="ColorSchemeDark">Tmavá</system:String>
<system:String x:Key="SoundEffect">Zvukový efekt</system:String> <system:String x:Key="SoundEffect">Zvukový efekt</system:String>
<system:String x:Key="SoundEffectTip">Po otvorení okna vyhľadávania prehrať krátky zvuk</system:String> <system:String x:Key="SoundEffectTip">Po otvorení okna vyhľadávania prehrať krátky zvuk</system:String>
<system:String x:Key="SoundEffectVolume">Hlasitosť zvukového efektu</system:String>
<system:String x:Key="SoundEffectVolumeTip">Upraviť hlasitosť zvukového efektu</system:String>
<system:String x:Key="Animation">Animácia</system:String> <system:String x:Key="Animation">Animácia</system:String>
<system:String x:Key="AnimationTip">Animovať používateľské rozhranie</system:String> <system:String x:Key="AnimationTip">Animovať používateľské rozhranie</system:String>
<system:String x:Key="AnimationSpeed">Rýchlosť animácie</system:String> <system:String x:Key="AnimationSpeed">Rýchlosť animácie</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Dark</system:String> <system:String x:Key="ColorSchemeDark">Dark</system:String>
<system:String x:Key="SoundEffect">Sound Effect</system:String> <system:String x:Key="SoundEffect">Sound Effect</system:String>
<system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String> <system:String x:Key="SoundEffectTip">Play a small sound when the search window opens</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animation</system:String> <system:String x:Key="Animation">Animation</system:String>
<system:String x:Key="AnimationTip">Use Animation in UI</system:String> <system:String x:Key="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Koyu</system:String> <system:String x:Key="ColorSchemeDark">Koyu</system:String>
<system:String x:Key="SoundEffect">Ses Efekti</system:String> <system:String x:Key="SoundEffect">Ses Efekti</system:String>
<system:String x:Key="SoundEffectTip">Arama penceresi açıldığında küçük bir ses oynat</system:String> <system:String x:Key="SoundEffectTip">Arama penceresi açıldığında küçük bir ses oynat</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">Animasyon</system:String> <system:String x:Key="Animation">Animasyon</system:String>
<system:String x:Key="AnimationTip">Arayüzde Animasyon Kullan</system:String> <system:String x:Key="AnimationTip">Arayüzde Animasyon Kullan</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">Темна</system:String> <system:String x:Key="ColorSchemeDark">Темна</system:String>
<system:String x:Key="SoundEffect">Звуковий ефект</system:String> <system:String x:Key="SoundEffect">Звуковий ефект</system:String>
<system:String x:Key="SoundEffectTip">Відтворювати невеликий звук при відкритті вікна пошуку</system:String> <system:String x:Key="SoundEffectTip">Відтворювати невеликий звук при відкритті вікна пошуку</system:String>
<system:String x:Key="SoundEffectVolume">Гучність звукового ефекту</system:String>
<system:String x:Key="SoundEffectVolumeTip">Налаштуйте гучність звукового ефекту</system:String>
<system:String x:Key="Animation">Анімація</system:String> <system:String x:Key="Animation">Анімація</system:String>
<system:String x:Key="AnimationTip">Використовувати анімацію в інтерфейсі</system:String> <system:String x:Key="AnimationTip">Використовувати анімацію в інтерфейсі</system:String>
<system:String x:Key="AnimationSpeed">Швидкість анімації</system:String> <system:String x:Key="AnimationSpeed">Швидкість анімації</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">深色</system:String> <system:String x:Key="ColorSchemeDark">深色</system:String>
<system:String x:Key="SoundEffect">音效</system:String> <system:String x:Key="SoundEffect">音效</system:String>
<system:String x:Key="SoundEffectTip">启用激活音效</system:String> <system:String x:Key="SoundEffectTip">启用激活音效</system:String>
<system:String x:Key="SoundEffectVolume">音效音量</system:String>
<system:String x:Key="SoundEffectVolumeTip">调整音效音量</system:String>
<system:String x:Key="Animation">动画</system:String> <system:String x:Key="Animation">动画</system:String>
<system:String x:Key="AnimationTip">启用动画</system:String> <system:String x:Key="AnimationTip">启用动画</system:String>
<system:String x:Key="AnimationSpeed">动画速度</system:String> <system:String x:Key="AnimationSpeed">动画速度</system:String>

View file

@ -154,6 +154,8 @@
<system:String x:Key="ColorSchemeDark">暗色系</system:String> <system:String x:Key="ColorSchemeDark">暗色系</system:String>
<system:String x:Key="SoundEffect">音效</system:String> <system:String x:Key="SoundEffect">音效</system:String>
<system:String x:Key="SoundEffectTip">搜尋窗口打開時播放音效</system:String> <system:String x:Key="SoundEffectTip">搜尋窗口打開時播放音效</system:String>
<system:String x:Key="SoundEffectVolume">Sound Effect Volume</system:String>
<system:String x:Key="SoundEffectVolumeTip">Adjust the volume of the sound effect</system:String>
<system:String x:Key="Animation">動畫</system:String> <system:String x:Key="Animation">動畫</system:String>
<system:String x:Key="AnimationTip">使用介面動畫</system:String> <system:String x:Key="AnimationTip">使用介面動畫</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String> <system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -46,47 +46,14 @@
<Window.InputBindings> <Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding EscCommand}" /> <KeyBinding Key="Escape" Command="{Binding EscCommand}" />
<KeyBinding Key="F5" Command="{Binding ReloadPluginDataCommand}" /> <KeyBinding Key="F5" Command="{Binding ReloadPluginDataCommand}" />
<KeyBinding Key="Tab" Command="{Binding AutocompleteQueryCommand}" />
<KeyBinding
Key="Tab"
Command="{Binding AutocompleteQueryCommand}"
Modifiers="Shift" />
<KeyBinding
Key="I"
Command="{Binding OpenSettingCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="N"
Command="{Binding SelectNextItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="J"
Command="{Binding SelectNextItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="D"
Command="{Binding SelectNextPageCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="P"
Command="{Binding SelectPrevItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="K"
Command="{Binding SelectPrevItemCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="U"
Command="{Binding SelectPrevPageCommand}"
Modifiers="Ctrl" />
<KeyBinding <KeyBinding
Key="Home" Key="Home"
Command="{Binding SelectFirstResultCommand}" Command="{Binding SelectFirstResultCommand}"
Modifiers="Alt" /> Modifiers="Alt" />
<KeyBinding <KeyBinding
Key="O" Key="End"
Command="{Binding LoadContextMenuCommand}" Command="{Binding SelectLastResultCommand}"
Modifiers="Ctrl" /> Modifiers="Alt" />
<KeyBinding <KeyBinding
Key="R" Key="R"
Command="{Binding ReQueryCommand}" Command="{Binding ReQueryCommand}"
@ -111,10 +78,6 @@
Key="OemMinus" Key="OemMinus"
Command="{Binding DecreaseMaxResultCommand}" Command="{Binding DecreaseMaxResultCommand}"
Modifiers="Control" /> Modifiers="Control" />
<KeyBinding
Key="H"
Command="{Binding LoadHistoryCommand}"
Modifiers="Ctrl" />
<KeyBinding <KeyBinding
Key="Enter" Key="Enter"
Command="{Binding OpenResultCommand}" Command="{Binding OpenResultCommand}"
@ -188,12 +151,52 @@
Modifiers="Ctrl" /> Modifiers="Ctrl" />
<KeyBinding <KeyBinding
Key="C" Key="C"
Modifiers="Ctrl+Shift" Command="{Binding CopyAlternativeCommand}"
Command="{Binding CopyAlternativeCommand}" /> Modifiers="Ctrl+Shift" />
<KeyBinding <KeyBinding
Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding TogglePreviewCommand}" Command="{Binding TogglePreviewCommand}"
Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding AutoCompleteHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding AutocompleteQueryCommand}"
Modifiers="{Binding AutoCompleteHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding AutoCompleteHotkey2, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding AutocompleteQueryCommand}"
Modifiers="{Binding AutoCompleteHotkey2, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SelectNextItemHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectNextItemCommand}"
Modifiers="{Binding SelectNextItemHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SelectPrevItemHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectPrevItemCommand}"
Modifiers="{Binding SelectPrevItemHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SelectNextItemHotkey2, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectNextItemCommand}"
Modifiers="{Binding SelectNextItemHotkey2, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SelectPrevItemHotkey2, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectPrevItemCommand}"
Modifiers="{Binding SelectPrevItemHotkey2, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SettingWindowHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding OpenSettingCommand}"
Modifiers="{Binding SettingWindowHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding OpenContextMenuHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding LoadContextMenuCommand}"
Modifiers="{Binding OpenContextMenuHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SelectNextPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectNextPageCommand}"
Modifiers="{Binding SelectNextPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding SelectPrevPageCommand}"
Modifiers="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
</Window.InputBindings> </Window.InputBindings>
<Grid> <Grid>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}"> <Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
@ -216,6 +219,7 @@
<TextBox <TextBox
x:Name="QueryTextBox" x:Name="QueryTextBox"
AllowDrop="True" AllowDrop="True"
AutomationProperties.Name="{Binding Results.SelectedItem.Result.Title}"
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}" InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}" InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
PreviewDragOver="OnPreviewDragOver" PreviewDragOver="OnPreviewDragOver"

View file

@ -26,6 +26,9 @@ using Key = System.Windows.Input.Key;
using System.Media; using System.Media;
using static Flow.Launcher.ViewModel.SettingWindowViewModel; using static Flow.Launcher.ViewModel.SettingWindowViewModel;
using DataObject = System.Windows.DataObject; using DataObject = System.Windows.DataObject;
using System.Windows.Media;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace Flow.Launcher namespace Flow.Launcher
{ {
@ -33,14 +36,17 @@ namespace Flow.Launcher
{ {
#region Private Fields #region Private Fields
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr SetForegroundWindow(IntPtr hwnd);
private readonly Storyboard _progressBarStoryboard = new Storyboard(); private readonly Storyboard _progressBarStoryboard = new Storyboard();
private bool isProgressBarStoryboardPaused; private bool isProgressBarStoryboardPaused;
private Settings _settings; private Settings _settings;
private NotifyIcon _notifyIcon; private NotifyIcon _notifyIcon;
private ContextMenu contextMenu; private ContextMenu contextMenu = new ContextMenu();
private MainViewModel _viewModel; private MainViewModel _viewModel;
private bool _animating; private bool _animating;
SoundPlayer animationSound = new SoundPlayer(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav"); MediaPlayer animationSound = new MediaPlayer();
#endregion #endregion
@ -52,6 +58,9 @@ namespace Flow.Launcher
InitializeComponent(); InitializeComponent();
InitializePosition(); InitializePosition();
animationSound.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav"));
DataObject.AddPastingHandler(QueryTextBox, OnPaste); DataObject.AddPastingHandler(QueryTextBox, OnPaste);
} }
@ -128,6 +137,8 @@ namespace Flow.Launcher
{ {
if (_settings.UseSound) if (_settings.UseSound)
{ {
animationSound.Position = TimeSpan.Zero;
animationSound.Volume = _settings.SoundVolume / 100.0;
animationSound.Play(); animationSound.Play();
} }
UpdatePosition(); UpdatePosition();
@ -264,12 +275,11 @@ namespace Flow.Launcher
{ {
_notifyIcon = new NotifyIcon _notifyIcon = new NotifyIcon
{ {
Text = Infrastructure.Constant.FlowLauncher, Text = Infrastructure.Constant.FlowLauncherFullName,
Icon = Properties.Resources.app, Icon = Properties.Resources.app,
Visible = !_settings.HideNotifyIcon Visible = !_settings.HideNotifyIcon
}; };
contextMenu = new ContextMenu();
var openIcon = new FontIcon var openIcon = new FontIcon
{ {
@ -277,7 +287,8 @@ namespace Flow.Launcher
}; };
var open = new MenuItem var open = new MenuItem
{ {
Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", Icon = openIcon Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")",
Icon = openIcon
}; };
var gamemodeIcon = new FontIcon var gamemodeIcon = new FontIcon
{ {
@ -285,7 +296,8 @@ namespace Flow.Launcher
}; };
var gamemode = new MenuItem var gamemode = new MenuItem
{ {
Header = InternationalizationManager.Instance.GetTranslation("GameMode"), Icon = gamemodeIcon Header = InternationalizationManager.Instance.GetTranslation("GameMode"),
Icon = gamemodeIcon
}; };
var positionresetIcon = new FontIcon var positionresetIcon = new FontIcon
{ {
@ -293,7 +305,8 @@ namespace Flow.Launcher
}; };
var positionreset = new MenuItem var positionreset = new MenuItem
{ {
Header = InternationalizationManager.Instance.GetTranslation("PositionReset"), Icon = positionresetIcon Header = InternationalizationManager.Instance.GetTranslation("PositionReset"),
Icon = positionresetIcon
}; };
var settingsIcon = new FontIcon var settingsIcon = new FontIcon
{ {
@ -301,7 +314,8 @@ namespace Flow.Launcher
}; };
var settings = new MenuItem var settings = new MenuItem
{ {
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"), Icon = settingsIcon Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"),
Icon = settingsIcon
}; };
var exitIcon = new FontIcon var exitIcon = new FontIcon
{ {
@ -309,7 +323,8 @@ namespace Flow.Launcher
}; };
var exit = new MenuItem var exit = new MenuItem
{ {
Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"), Icon = exitIcon Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"),
Icon = exitIcon
}; };
open.Click += (o, e) => _viewModel.ToggleFlowLauncher(); open.Click += (o, e) => _viewModel.ToggleFlowLauncher();
@ -327,7 +342,6 @@ namespace Flow.Launcher
contextMenu.Items.Add(settings); contextMenu.Items.Add(settings);
contextMenu.Items.Add(exit); contextMenu.Items.Add(exit);
_notifyIcon.ContextMenuStrip = new ContextMenuStrip(); // it need for close the context menu. if not, context menu can't close.
_notifyIcon.MouseClick += (o, e) => _notifyIcon.MouseClick += (o, e) =>
{ {
switch (e.Button) switch (e.Button)
@ -335,9 +349,15 @@ namespace Flow.Launcher
case MouseButtons.Left: case MouseButtons.Left:
_viewModel.ToggleFlowLauncher(); _viewModel.ToggleFlowLauncher();
break; break;
case MouseButtons.Right: case MouseButtons.Right:
contextMenu.IsOpen = true; contextMenu.IsOpen = true;
// Get context menu handle and bring it to the foreground
if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource)
{
_ = SetForegroundWindow(hwndSource.Handle);
}
contextMenu.Focus();
break; break;
} }
}; };

View file

@ -311,6 +311,8 @@ namespace Flow.Launcher
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback); public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback);
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Remove(callback); public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Remove(callback);
public void ReQuery(bool reselect = true) => _mainVM.ReQuery(reselect);
#endregion #endregion
#region Private Methods #region Private Methods

View file

@ -0,0 +1,117 @@
<UserControl
x:Class="Flow.Launcher.Resources.Controls.Card"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Border x:Name="BD" HorizontalAlignment="Stretch">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="{DynamicResource Color00B}" />
<Setter Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="MinHeight" Value="68" />
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:Card}}" Value="Inside">
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Background" Value="Transparent" />
</DataTrigger>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:Card}}" Value="InsideFit">
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Padding" Value="38,0,26,0" />
<Setter Property="Background" Value="Transparent" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="auto"
MinWidth="20"
MaxWidth="60" />
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="Auto" MinWidth="30" />
</Grid.ColumnDefinitions>
<ContentControl
Grid.Row="0"
Grid.Column="2"
Margin="0,0,16,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Width" Value="Auto" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</StackPanel.Style>
<TextBlock x:Name="ItemTitle" Text="{Binding Title, RelativeSource={RelativeSource AncestorType=local:Card}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock x:Name="SubTitle" Text="{Binding Sub, RelativeSource={RelativeSource AncestorType=local:Card}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SubTitle, Path=Text}" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Foreground" Value="{DynamicResource Color04B}" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Padding" Value="0,0,24,0" />
<Setter Property="TextWrapping" Value="WrapWithOverflow" />
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<TextBlock x:Name="ItemIcon" Text="{Binding Icon, RelativeSource={RelativeSource AncestorType=local:Card}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ItemIcon, Path=Text}" Value="{x:Static sys:String.Empty}">
<Setter Property="Margin" Value="24,0,0,0" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Margin" Value="24,0,16,0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="20" />
<Setter Property="FontFamily" Value="/Resources/#Segoe Fluent Icons" />
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</ControlTemplate>
</UserControl.Template>
</UserControl>

View file

@ -0,0 +1,63 @@
using System.Windows;
using UserControl = System.Windows.Controls.UserControl;
namespace Flow.Launcher.Resources.Controls
{
public partial class Card : UserControl
{
public enum CardType
{
Default,
Inside,
InsideFit
}
public Card()
{
InitializeComponent();
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(nameof(Title), typeof(string), typeof(Card), new PropertyMetadata(string.Empty));
public string Sub
{
get { return (string)GetValue(SubProperty); }
set { SetValue(SubProperty, value); }
}
public static readonly DependencyProperty SubProperty =
DependencyProperty.Register(nameof(Sub), typeof(string), typeof(Card), new PropertyMetadata(string.Empty));
public string Icon
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register(nameof(Icon), typeof(string), typeof(Card), new PropertyMetadata(string.Empty));
/// <summary>
/// Gets or sets additional content for the UserControl
/// </summary>
public object AdditionalContent
{
get { return (object)GetValue(AdditionalContentProperty); }
set { SetValue(AdditionalContentProperty, value); }
}
public static readonly DependencyProperty AdditionalContentProperty =
DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(Card),
new PropertyMetadata(null));
public CardType Type
{
get { return (CardType)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register(nameof(Type), typeof(CardType), typeof(Card),
new PropertyMetadata(CardType.Default));
}
}

View file

@ -0,0 +1,313 @@
<UserControl
x:Class="Flow.Launcher.Resources.Controls.ExCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
mc:Ignorable="d">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Expander
x:Name="expanderHeader"
Padding="0"
BorderThickness="1"
IsExpanded="{Binding Mode=TwoWay, Path=IsExpanded}"
SnapsToDevicePixels="False">
<Expander.Style>
<Style TargetType="{x:Type Expander}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource Color00B}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="BorderBrush" Value="{DynamicResource Color03B}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border
x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"
SnapsToDevicePixels="true">
<DockPanel>
<ToggleButton
x:Name="HeaderSite"
MinWidth="0"
MinHeight="68"
Margin="0,0,0,0"
Padding="0,0,0,0"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
DockPanel.Dock="Top"
FocusVisualStyle="{DynamicResource ExpanderHeaderFocusVisual}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStretch="{TemplateBinding FontStretch}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border
x:Name="ToggleBtn"
Padding="{TemplateBinding Padding}"
Background="{DynamicResource Color00B}"
ClipToBounds="True"
CornerRadius="5">
<Grid SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<ContentPresenter
Grid.Column="0"
Margin="0,0,0,0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
RecognizesAccessKey="True"
SnapsToDevicePixels="True" />
<Grid
x:Name="ChevronGrid"
Grid.Column="2"
Margin="0,0,18,0"
VerticalAlignment="Center"
Background="Transparent"
RenderTransformOrigin="0.5, 0.5">
<Grid.RenderTransform>
<RotateTransform Angle="0" />
</Grid.RenderTransform>
<Ellipse
x:Name="circle"
Width="19"
Height="19"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stroke="Transparent" />
<Path
x:Name="arrow"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 1,1.5 L 4.5,5 L 8,1.5"
SnapsToDevicePixels="false"
Stroke="#666"
StrokeThickness="1" />
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="arrow" Property="Data" Value="M 1,4.5 L 4.5,1 L 8,4.5" />
<Setter TargetName="ToggleBtn" Property="CornerRadius" Value="5 5 0 0" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="circle" Property="Stroke" Value="Transparent" />
<Setter TargetName="arrow" Property="Stroke" Value="{DynamicResource Color05B}" />
<Setter TargetName="ToggleBtn" Property="Background" Value="{DynamicResource CustomExpanderHover}" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="circle" Property="Stroke" Value="Transparent" />
<Setter TargetName="circle" Property="StrokeThickness" Value="1.5" />
<Setter TargetName="arrow" Property="Stroke" Value="{DynamicResource Color17B}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
<Border x:Name="ContentPresenterBorder" BorderThickness="0">
<ContentPresenter
x:Name="ExpandSite"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
DockPanel.Dock="Bottom"
Focusable="false" />
<Border.LayoutTransform>
<ScaleTransform ScaleY="0" />
</Border.LayoutTransform>
</Border>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter TargetName="ExpandSite" Property="Visibility" Value="Visible" />
<Setter TargetName="ContentPresenterBorder" Property="BorderThickness" Value="0,0,0,0" />
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ContentPresenterBorder"
Storyboard.TargetProperty="(Border.LayoutTransform).(ScaleTransform.ScaleY)"
From="0.0"
To="1.0"
Duration="00:00:00.00" />
<DoubleAnimation
Storyboard.TargetName="ContentPresenterBorder"
Storyboard.TargetProperty="(Border.Opacity)"
From="0.0"
To="1.0"
Duration="00:00:00.00" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ContentPresenterBorder"
Storyboard.TargetProperty="(Border.LayoutTransform).(ScaleTransform.ScaleY)"
From="1.0"
To="0"
Duration="00:00:00.00" />
<!-- Animation 00:00:00.167 -->
<DoubleAnimation
Storyboard.TargetName="ContentPresenterBorder"
Storyboard.TargetProperty="(Border.Opacity)"
From="1.0"
To="0.0"
Duration="00:00:00.00" />
<!-- Animation 00:00:00.167 -->
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter TargetName="ExpandSite" Property="DockPanel.Dock" Value="Right" />
<Setter TargetName="HeaderSite" Property="DockPanel.Dock" Value="Left" />
<Setter TargetName="HeaderSite" Property="Style" Value="{StaticResource ExpanderRightHeaderStyle}" />
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter TargetName="ExpandSite" Property="DockPanel.Dock" Value="Top" />
<Setter TargetName="HeaderSite" Property="DockPanel.Dock" Value="Bottom" />
<Setter TargetName="HeaderSite" Property="Style" Value="{StaticResource ExpanderUpHeaderStyle}" />
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter TargetName="ExpandSite" Property="DockPanel.Dock" Value="Left" />
<Setter TargetName="HeaderSite" Property="DockPanel.Dock" Value="Right" />
<Setter TargetName="HeaderSite" Property="Style" Value="{StaticResource ExpanderLeftHeaderStyle}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Expander.Style>
<Expander.Header>
<Border Margin="0" Padding="0,12,0,12">
<Grid Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}}, Path=ActualWidth}" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="auto"
MinWidth="20"
MaxWidth="60" />
<ColumnDefinition Width="7*" />
<ColumnDefinition Width="Auto" MinWidth="30" />
<ColumnDefinition Width="Auto" MinWidth="30" />
</Grid.ColumnDefinitions>
<ContentControl
x:Name="firstContentPresenter"
Grid.Column="2"
Margin="0,0,14,0"
HorizontalAlignment="Right"
Content="{Binding SideContent, RelativeSource={RelativeSource AncestorType=local:ExCard}}" />
<TextBlock
x:Name="ItemIcon"
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Icon, RelativeSource={RelativeSource AncestorType=local:ExCard}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ItemIcon, Path=Text}" Value="{x:Static sys:String.Empty}">
<Setter Property="Margin" Value="24,0,0,0" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Margin" Value="24,0,16,0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="20" />
<Setter Property="FontFamily" Value="/Resources/#Segoe Fluent Icons" />
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
</Style>
</TextBlock.Style>
</TextBlock>
<StackPanel Grid.Column="1" Margin="0,0,14,0">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Width" Value="Auto" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</StackPanel.Style>
<TextBlock x:Name="ItemTitle" Text="{Binding Title, RelativeSource={RelativeSource AncestorType=local:ExCard}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock x:Name="SubTitle" Text="{Binding Sub, RelativeSource={RelativeSource AncestorType=local:ExCard}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SubTitle, Path=Text}" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Foreground" Value="{DynamicResource Color04B}" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Padding" Value="0,0,24,0" />
<Setter Property="TextWrapping" Value="WrapWithOverflow" />
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Grid>
</Border>
</Expander.Header>
<Grid
Grid.Column="0"
Grid.ColumnSpan="4"
HorizontalAlignment="Stretch"
FlowDirection="LeftToRight">
<StackPanel Margin="0,0,0,0" Orientation="Vertical">
<ContentControl
Grid.Column="0"
Grid.ColumnSpan="4"
Margin="0,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</StackPanel>
</Grid>
</Expander>
</ControlTemplate>
</UserControl.Template>
</UserControl>

View file

@ -0,0 +1,57 @@
using System.Windows;
using System.Windows.Controls;
namespace Flow.Launcher.Resources.Controls
{
public partial class ExCard : UserControl
{
public ExCard()
{
InitializeComponent();
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(nameof(Title), typeof(string), typeof(ExCard), new PropertyMetadata(string.Empty));
public string Sub
{
get { return (string)GetValue(SubProperty); }
set { SetValue(SubProperty, value); }
}
public static readonly DependencyProperty SubProperty =
DependencyProperty.Register(nameof(Sub), typeof(string), typeof(ExCard), new PropertyMetadata(string.Empty));
public string Icon
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register(nameof(Icon), typeof(string), typeof(ExCard), new PropertyMetadata(string.Empty));
/// <summary>
/// Gets or sets additional content for the UserControl
/// </summary>
public object AdditionalContent
{
get { return (object)GetValue(AdditionalContentProperty); }
set { SetValue(AdditionalContentProperty, value); }
}
public static readonly DependencyProperty AdditionalContentProperty =
DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(ExCard),
new PropertyMetadata(null));
public object SideContent
{
get { return (object)GetValue(SideContentProperty); }
set { SetValue(SideContentProperty, value); }
}
public static readonly DependencyProperty SideContentProperty =
DependencyProperty.Register(nameof(SideContent), typeof(object), typeof(ExCard),
new PropertyMetadata(null));
}
}

View file

@ -0,0 +1,74 @@
<UserControl
x:Class="Flow.Launcher.Resources.Controls.HotkeyDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Button
Width="Auto"
FontSize="13"
FontWeight="Bold"
Foreground="{DynamicResource Color01B}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border
x:Name="ButtonBorder"
BorderBrush="{DynamicResource ButtonInsideBorder}"
CornerRadius="5">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="{DynamicResource Color12B}" />
<Setter Property="Padding" Value="5,0,5,0" />
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:HotkeyDisplay}}" Value="Small">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="BorderThickness" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Button.Template>
<Button.Content>
<ItemsControl x:Name="KeysControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" CornerRadius="5">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="Padding" Value="10,5,10,5" />
<Setter Property="Margin" Value="2,5,2,5" />
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource AncestorType=local:HotkeyDisplay}}" Value="Small">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="Padding" Value="10,5,10,5" />
<Setter Property="Margin" Value="2,0,2,0" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Foreground="{DynamicResource AccentButtonForegroundPointerOver}" Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Button.Content>
</Button>
</Grid>
</UserControl>

View file

@ -0,0 +1,63 @@
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace Flow.Launcher.Resources.Controls
{
public partial class HotkeyDisplay : UserControl
{
public enum DisplayType
{
Default,
Small
}
public HotkeyDisplay()
{
InitializeComponent();
//List<string> stringList =e.NewValue.Split('+').ToList();
Values = new ObservableCollection<string>();
KeysControl.ItemsSource = Values;
}
public string Keys
{
get { return (string)GetValue(KeysProperty); }
set { SetValue(KeysProperty, value); }
}
public static readonly DependencyProperty KeysProperty =
DependencyProperty.Register(nameof(Keys), typeof(string), typeof(HotkeyDisplay),
new PropertyMetadata(string.Empty, keyChanged));
public DisplayType Type
{
get { return (DisplayType)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register(nameof(Type), typeof(DisplayType), typeof(HotkeyDisplay),
new PropertyMetadata(DisplayType.Default));
private static void keyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as UserControl;
if (null == control) return; // This should not be possible
var newValue = e.NewValue as string;
if (null == newValue) return;
if (d is not HotkeyDisplay hotkeyDisplay)
return;
hotkeyDisplay.Values.Clear();
foreach (var key in newValue.Split('+'))
{
hotkeyDisplay.Values.Add(key);
}
}
public ObservableCollection<string> Values { get; set; }
}
}

View file

@ -3283,5 +3283,416 @@
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<!-- Content Dialog -->
<Style x:Key="ContentDialog" TargetType="ui:ContentDialog">
<Setter Property="Foreground" Value="{DynamicResource ContentDialogForeground}" />
<Setter Property="Background" Value="{DynamicResource ContentDialogBackground}" />
<Setter Property="BorderThickness" Value="{DynamicResource ContentDialogBorderWidth}" />
<Setter Property="BorderBrush" Value="{DynamicResource ContentDialogBorderBrush}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
<Setter Property="PrimaryButtonStyle" Value="{DynamicResource DefaultButtonStyle}" />
<Setter Property="SecondaryButtonStyle" Value="{DynamicResource DefaultButtonStyle}" />
<Setter Property="CloseButtonStyle" Value="{DynamicResource DefaultButtonStyle}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="IsShadowEnabled" Value="{DynamicResource {x:Static SystemParameters.DropShadowKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ui:ContentDialog">
<Border x:Name="Container">
<VisualStateManager.CustomVisualStateManager>
<ui:SimpleVisualStateManager />
</VisualStateManager.CustomVisualStateManager>
<Grid
x:Name="LayoutRoot"
Background="{DynamicResource ContentDialogOverlayBG}"
SnapsToDevicePixels="True"
Visibility="Collapsed">
<Grid
x:Name="BackgroundElement"
MinWidth="{DynamicResource ContentDialogMinWidth}"
MinHeight="{DynamicResource ContentDialogMinHeight}"
MaxWidth="{DynamicResource ContentDialogMaxWidth}"
MaxHeight="{DynamicResource ContentDialogMaxHeight}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FlowDirection="{TemplateBinding FlowDirection}"
RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<ScaleTransform x:Name="ScaleTransform" />
</Grid.RenderTransform>
<ui:ThemeShadowChrome
x:Name="Shdw"
Margin="{DynamicResource ContentDialogBorderWidth}"
CornerRadius="{TemplateBinding CornerRadius}"
IsShadowEnabled="{TemplateBinding IsShadowEnabled}" />
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Border x:Name="DialogSpace" Padding="{DynamicResource ContentDialogPadding}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer
x:Name="ContentScrollViewer"
Margin="{DynamicResource ContentDialogContentScrollViewerMargin}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
IsTabStop="False"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl
x:Name="Title"
Margin="{DynamicResource ContentDialogTitleMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{TemplateBinding Title}"
ContentTemplate="{TemplateBinding TitleTemplate}"
FontFamily="{DynamicResource {x:Static SystemFonts.MessageFontFamilyKey}}"
FontSize="20"
FontWeight="Normal"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<ui:ContentPresenterEx
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextWrapping="Wrap" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<ui:ContentPresenterEx
x:Name="Content"
Grid.Row="1"
Margin="{DynamicResource ContentDialogContentMargin}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
TextElement.FontFamily="{DynamicResource ContentControlThemeFontFamily}"
TextElement.FontSize="{DynamicResource ControlContentThemeFontSize}"
TextElement.Foreground="{TemplateBinding Foreground}"
TextWrapping="Wrap" />
</Grid>
</ScrollViewer>
<Grid
x:Name="CommandSpace"
Grid.Row="1"
Margin="{DynamicResource ContentDialogCommandSpaceMargin}"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
KeyboardNavigation.DirectionalNavigation="Contained">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.5*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="PrimaryButton"
Grid.Column="0"
Margin="0,0,2,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{TemplateBinding PrimaryButtonText}"
IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}"
Style="{TemplateBinding PrimaryButtonStyle}" />
<Button
x:Name="SecondaryButton"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="2,0,2,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{TemplateBinding SecondaryButtonText}"
IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}"
Style="{TemplateBinding SecondaryButtonStyle}" />
<Button
x:Name="CloseButton"
Grid.Column="3"
Margin="2,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{TemplateBinding CloseButtonText}"
Style="{TemplateBinding CloseButtonStyle}" />
</Grid>
</Grid>
</Border>
</Border>
</Grid>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DialogShowingStates">
<VisualStateGroup.Transitions>
<VisualTransition To="DialogHidden">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="SnapsToDevicePixels">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False" />
</BooleanAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0" />
<SplineDoubleKeyFrame
KeySpline="0.1,0.9 0.2,1.0"
KeyTime="0:0:0.5"
Value="1.05" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0" />
<SplineDoubleKeyFrame
KeySpline="0.1,0.9 0.2,1.0"
KeyTime="0:0:0.5"
Value="1.05" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.0" />
<LinearDoubleKeyFrame KeyTime="0:0:0.083" Value="0.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="DialogShowing">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="SnapsToDevicePixels">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05" />
<SplineDoubleKeyFrame
KeySpline="0.1,0.9 0.2,1.0"
KeyTime="0:0:0.5"
Value="1.0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1.05" />
<SplineDoubleKeyFrame
KeySpline="0.1,0.9 0.2,1.0"
KeyTime="0:0:0.5"
Value="1.0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.0" />
<LinearDoubleKeyFrame KeyTime="0:0:0.167" Value="1.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="DialogHidden" />
<VisualState x:Name="DialogShowing">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement" Storyboard.TargetProperty="(KeyboardNavigation.TabNavigation)">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static KeyboardNavigationMode.Cycle}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="DialogShowingWithoutSmokeLayer">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Null}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DialogSizingStates">
<VisualState x:Name="DefaultDialogSizing" />
<VisualState x:Name="FullDialogSizing">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement" Storyboard.TargetProperty="VerticalAlignment">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static VerticalAlignment.Stretch}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ButtonsVisibilityStates">
<VisualState x:Name="AllVisible" />
<VisualState x:Name="NoneVisible">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="CommandSpace" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PrimaryVisible">
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="2,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SecondaryVisible">
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="2,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="CloseVisible">
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="2,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PrimaryAndSecondaryVisible">
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="2,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PrimaryAndCloseVisible">
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="2,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SecondaryAndCloseVisible">
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="0" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="SecondaryButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="0,0,2,0" />
</ThicknessAnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="(Grid.ColumnSpan)">
<DiscreteInt32KeyFrame KeyTime="0:0:0" Value="2" />
</Int32AnimationUsingKeyFrames>
<ThicknessAnimationUsingKeyFrames Storyboard.TargetName="CloseButton" Storyboard.TargetProperty="Margin">
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="2,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PrimaryButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DefaultButtonStates">
<VisualState x:Name="NoDefaultButton" />
<VisualState x:Name="PrimaryAsDefaultButton" />
<VisualState x:Name="SecondaryAsDefaultButton" />
<VisualState x:Name="CloseAsDefaultButton" />
</VisualStateGroup>
<VisualStateGroup x:Name="DialogBorderStates">
<VisualState x:Name="NoBorder" />
<VisualState x:Name="AccentColorBorder">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{DynamicResource SystemControlForegroundAccentBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsShadowEnabled" Value="False">
<Setter TargetName="Shdw" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="DefaultButton" Value="Primary">
<Setter TargetName="PrimaryButton" Property="Style" Value="{DynamicResource AccentButtonStyle}" />
</Trigger>
<Trigger Property="DefaultButton" Value="Secondary">
<Setter TargetName="SecondaryButton" Property="Style" Value="{DynamicResource AccentButtonStyle}" />
</Trigger>
<Trigger Property="DefaultButton" Value="Close">
<Setter TargetName="CloseButton" Property="Style" Value="{DynamicResource AccentButtonStyle}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary> </ResourceDictionary>

View file

@ -104,6 +104,16 @@
<Color x:Key="NumberBoxColor26">#ffffff</Color> <Color x:Key="NumberBoxColor26">#ffffff</Color>
<Color x:Key="HoverStoreGrid">#272727</Color> <Color x:Key="HoverStoreGrid">#272727</Color>
<!-- Resources for HotkeyControl -->
<SolidColorBrush x:Key="CustomHotkeyHover" Color="#323232" />
<!-- Resources for Expander -->
<SolidColorBrush x:Key="CustomExpanderHover" Color="#323232" />
<!-- Resource for ContentDialog -->
<SolidColorBrush x:Key="ContentDialogOverlayBG" Color="#4D000000" />
<!-- Infobar Warning -->
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#FCE100" />
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#433519" />
<SolidColorBrush x:Key="InfoBarBD" Color="#19000000" />
<SolidColorBrush x:Key="ButtonOutBorder" Color="Transparent" /> <SolidColorBrush x:Key="ButtonOutBorder" Color="Transparent" />
<SolidColorBrush x:Key="ButtonInsideBorder" Color="#3f3f3f" /> <SolidColorBrush x:Key="ButtonInsideBorder" Color="#3f3f3f" />
@ -772,7 +782,7 @@
<m:StaticResource x:Key="ContentDialogForeground" ResourceKey="SystemControlPageTextBaseHighBrush" /> <m:StaticResource x:Key="ContentDialogForeground" ResourceKey="SystemControlPageTextBaseHighBrush" />
<m:StaticResource x:Key="ContentDialogBackground" ResourceKey="SystemControlPageBackgroundAltHighBrush" /> <m:StaticResource x:Key="ContentDialogBackground" ResourceKey="SystemControlPageBackgroundAltHighBrush" />
<m:StaticResource x:Key="ContentDialogBorderBrush" ResourceKey="SystemControlBackgroundBaseLowBrush" /> <m:StaticResource x:Key="ContentDialogBorderBrush" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<m:StaticResource x:Key="ContentDialogLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" /> <m:StaticResource x:Key="ContentDialogLightDismissOverlayBackground" ResourceKey="ContentDialogOverlayBG" />
<!-- Resources for DataGrid --> <!-- Resources for DataGrid -->
<sys:Double x:Key="ListAccentLowOpacity">0.6</sys:Double> <sys:Double x:Key="ListAccentLowOpacity">0.6</sys:Double>

View file

@ -95,6 +95,17 @@
<Color x:Key="NumberBoxColor26">#1b1b1b</Color> <Color x:Key="NumberBoxColor26">#1b1b1b</Color>
<Color x:Key="HoverStoreGrid">#f6f6f6</Color> <Color x:Key="HoverStoreGrid">#f6f6f6</Color>
<!-- Resources for HotkeyControl -->
<SolidColorBrush x:Key="CustomHotkeyHover" Color="#f6f6f6" />
<!-- Resources for Expander -->
<SolidColorBrush x:Key="CustomExpanderHover" Color="#f6f6f6" />
<!-- Resource for ContentDialog -->
<SolidColorBrush x:Key="ContentDialogOverlayBG" Color="#4D000000" />
<!-- Infobar Warning -->
<SolidColorBrush x:Key="InfoBarWarningIcon" Color="#9D5D00" />
<SolidColorBrush x:Key="InfoBarWarningBG" Color="#FFF4CE" />
<SolidColorBrush x:Key="InfoBarBD" Color="#0F000000" />
<SolidColorBrush x:Key="ButtonOutBorder" Color="#e5e5e5" /> <SolidColorBrush x:Key="ButtonOutBorder" Color="#e5e5e5" />
<SolidColorBrush x:Key="ButtonInsideBorder" Color="#d3d3d3" /> <SolidColorBrush x:Key="ButtonInsideBorder" Color="#d3d3d3" />

View file

@ -9,6 +9,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:ui="http://schemas.modernwpf.com/2019"
Title="WelcomePage2" Title="WelcomePage2"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <Page.Resources>
<converters:BorderClipConverter x:Key="BorderClipConverter" /> <converters:BorderClipConverter x:Key="BorderClipConverter" />
@ -110,12 +111,13 @@
FontWeight="SemiBold" FontWeight="SemiBold"
Text="{DynamicResource flowlauncherHotkey}" /> Text="{DynamicResource flowlauncherHotkey}" />
<flowlauncher:HotkeyControl <flowlauncher:HotkeyControl
x:Name="HotkeyControl" Margin="0,8,0,0"
Width="300" ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
Height="35" DefaultHotkey="Alt+Space"
Margin="-206,10,0,0" Hotkey="{Binding Settings.Hotkey}"
GotFocus="HotkeyControl_OnGotFocus" HotkeySettings="{Binding Settings}"
LostFocus="HotkeyControl_OnLostFocus"/> ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

View file

@ -5,16 +5,14 @@ using System;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Navigation; using System.Windows.Navigation;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Resources.Pages namespace Flow.Launcher.Resources.Pages
{ {
public partial class WelcomePage2 public partial class WelcomePage2
{ {
private Settings Settings { get; set; } public Settings Settings { get; set; }
private Brush tbMsgForegroundColorOriginal;
private string tbMsgTextOriginal;
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
@ -22,31 +20,14 @@ namespace Flow.Launcher.Resources.Pages
Settings = settings; Settings = settings;
else else
throw new ArgumentException("Unexpected Parameter setting."); throw new ArgumentException("Unexpected Parameter setting.");
InitializeComponent(); InitializeComponent();
tbMsgTextOriginal = HotkeyControl.tbMsg.Text;
tbMsgForegroundColorOriginal = HotkeyControl.tbMsg.Foreground;
HotkeyControl.SetHotkeyAsync(Settings.Hotkey, false);
} }
private void HotkeyControl_OnGotFocus(object sender, RoutedEventArgs args)
{
HotKeyMapper.RemoveHotkey(Settings.Hotkey);
}
private void HotkeyControl_OnLostFocus(object sender, RoutedEventArgs args)
{
if (HotkeyControl.CurrentHotkeyAvailable)
{
HotKeyMapper.SetHotkey(HotkeyControl.CurrentHotkey, HotKeyMapper.OnToggleHotkey);
Settings.Hotkey = HotkeyControl.CurrentHotkey.ToString();
}
else
{
HotKeyMapper.SetHotkey(new HotkeyModel(Settings.Hotkey), HotKeyMapper.OnToggleHotkey);
}
HotkeyControl.tbMsg.Text = tbMsgTextOriginal; [RelayCommand]
HotkeyControl.tbMsg.Foreground = tbMsgForegroundColorOriginal; private static void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
} }
} }
} }

View file

@ -2,11 +2,13 @@
x:Class="Flow.Launcher.Resources.Pages.WelcomePage3" x:Class="Flow.Launcher.Resources.Pages.WelcomePage3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.Resources.Pages" xmlns:local="clr-namespace:Flow.Launcher.Resources.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:ui="http://schemas.modernwpf.com/2019"
Title="WelcomePage3" Title="WelcomePage3"
VerticalAlignment="Stretch"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <Page.Resources>
<Style x:Key="KbdLine" TargetType="Border"> <Style x:Key="KbdLine" TargetType="Border">
@ -27,301 +29,86 @@
<Setter Property="Foreground" Value="{DynamicResource Color05B}" /> <Setter Property="Foreground" Value="{DynamicResource Color05B}" />
</Style> </Style>
</Page.Resources> </Page.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <Grid>
<Grid> <Grid.RowDefinitions>
<Grid.RowDefinitions> <RowDefinition Height="Auto" />
<RowDefinition Height="0" /> <RowDefinition Height="*" />
<RowDefinition /> </Grid.RowDefinitions>
</Grid.RowDefinitions> <TextBlock
Grid.Row="0"
<Border Grid.Row="0" HorizontalAlignment="Stretch"> Margin="24,20,24,14"
<Border.Background> FontSize="20"
<LinearGradientBrush StartPoint="0 0" EndPoint="1 1"> FontWeight="SemiBold"
<LinearGradientBrush.GradientStops> Text="{DynamicResource Welcome_Page3_Title}" />
<GradientStop Offset="0.0" Color="#16af7b" /> <ScrollViewer
<GradientStop Offset="1.0" Color="#34c191" /> Grid.Row="1"
</LinearGradientBrush.GradientStops> Height="478"
</LinearGradientBrush> Margin="0,0,0,0"
</Border.Background> HorizontalAlignment="Stretch"
FontSize="13">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal"> <StackPanel Margin="24,0,24,0">
<Image <Border
Width="300" BorderBrush="{DynamicResource Color03B}"
Height="100" BorderThickness="0"
Margin="0,0,0,0" CornerRadius="5">
Source="../../images/page_img02.png" <StackPanel>
Style="{DynamicResource StyleImageFadeIn}" /> <cc:Card
</StackPanel> Title="{DynamicResource HotkeyUpDownDesc}"
</Border> BorderThickness="0,0,0,0"
Type="Inside">
<ScrollViewer
Grid.Row="1"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Grid>
<StackPanel Margin="24,20,24,20">
<StackPanel Margin="0,0,0,10">
<TextBlock
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource Welcome_Page3_Title}" />
</StackPanel>
<Border Style="{DynamicResource KbdLine}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel <cc:HotkeyDisplay Keys="←+→" Type="Small" />
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}">←</TextBlock>
</Border>
<TextBlock VerticalAlignment="Center">,</TextBlock>
<Border Margin="5,0,0,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}">→</TextBlock>
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyUpDownDesc}" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </cc:Card>
<cc:Card
<Border Style="{DynamicResource KbdLine}"> Title="{DynamicResource HotkeyLeftRightDesc}"
BorderThickness="0,0,0,0"
Type="Inside">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel <cc:HotkeyDisplay Keys="↑+↓" Type="Small" />
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}">↑</TextBlock>
</Border>
<TextBlock VerticalAlignment="Center">,</TextBlock>
<Border Margin="5,0,0,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}">↓</TextBlock>
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyLeftRightDesc}" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </cc:Card>
<cc:Card
<Border Style="{DynamicResource KbdLine}"> Title="{DynamicResource HotkeyESCDesc}"
BorderThickness="0,0,0,0"
Type="Inside">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel <cc:HotkeyDisplay Keys="ESC" Type="Small" />
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}">Enter</TextBlock>
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyRunDesc}" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </cc:Card>
<cc:Card
<Border Style="{DynamicResource KbdLine}"> Title="{DynamicResource HotkeyRunDesc}"
BorderThickness="0,0,0,0"
Type="Inside">
<cc:HotkeyDisplay Keys="ENTER" Type="Small" />
</cc:Card>
<cc:Card
Title="{DynamicResource HotkeyShiftEnterDesc}"
BorderThickness="0,0,0,0"
Type="Inside">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel <cc:HotkeyDisplay Keys="SHIFT+ENTER" Type="Small" />
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="ESC" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyESCDesc}" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </cc:Card>
<cc:Card
<Border Style="{DynamicResource KbdLine}"> Title="{DynamicResource HotkeyCtrlEnterDesc}"
BorderThickness="0,0,0,0"
Type="Inside">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel <cc:HotkeyDisplay Keys="CTRL+ENTER" Type="Small" />
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Tab" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyTabDesc}" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </cc:Card>
<cc:Card
<Border Style="{DynamicResource KbdLine}"> Title="{DynamicResource HotkeyCtrlShiftEnterDesc}"
BorderThickness="0,0,0,0"
Type="Inside">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel <cc:HotkeyDisplay Keys="CTRL+SHIFT+ENTER" Type="Small" />
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Shift" />
</Border>
<TextBlock VerticalAlignment="Center">+</TextBlock>
<Border Margin="5,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="ENTER" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyShiftEnterDesc}" />
</StackPanel>
</StackPanel> </StackPanel>
</Border> </cc:Card>
<Border Style="{DynamicResource KbdLine}">
<StackPanel Orientation="Horizontal">
<StackPanel
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Ctrl" />
</Border>
<TextBlock VerticalAlignment="Center">+</TextBlock>
<Border Margin="5,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="ENTER" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyCtrlEnterDesc}" />
</StackPanel>
</StackPanel>
</Border>
<Border Style="{DynamicResource KbdLine}">
<StackPanel Orientation="Horizontal">
<StackPanel
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Ctrl" />
</Border>
<TextBlock VerticalAlignment="Center">+</TextBlock>
<Border Margin="5,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Shift" />
</Border>
<TextBlock VerticalAlignment="Center">+</TextBlock>
<Border Margin="5,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="ENTER" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyCtrlShiftEnterDesc}" />
</StackPanel>
</StackPanel>
</Border>
<Border Style="{DynamicResource KbdLine}">
<StackPanel Orientation="Horizontal">
<StackPanel
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Ctrl" />
</Border>
<TextBlock VerticalAlignment="Center">+</TextBlock>
<Border Margin="5,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="H" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyCtrlHDesc}" />
</StackPanel>
</StackPanel>
</Border>
<Border Style="{DynamicResource KbdLine}">
<StackPanel Orientation="Horizontal">
<StackPanel
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="Ctrl" />
</Border>
<TextBlock VerticalAlignment="Center">+</TextBlock>
<Border Margin="5,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="I" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyCtrlIDesc}" />
</StackPanel>
</StackPanel>
</Border>
<Border Style="{DynamicResource KbdLine}">
<StackPanel Orientation="Horizontal">
<StackPanel
Width="210"
Margin="20,5,4,5"
VerticalAlignment="Center"
Orientation="Horizontal">
<Border Margin="0,0,5,0" Style="{DynamicResource Kbd}">
<TextBlock Style="{DynamicResource KbdText}" Text="F5" />
</Border>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock
VerticalAlignment="Center"
FontSize="13"
Text="{DynamicResource HotkeyF5Desc}" />
</StackPanel>
</StackPanel>
</Border>
</StackPanel> </StackPanel>
</Grid> </Border>
</ScrollViewer> </StackPanel>
</ScrollViewer>
</Grid> </Grid>
</ScrollViewer>
</ui:Page> </ui:Page>

Binary file not shown.

View file

@ -2,6 +2,7 @@
x:Class="Flow.Launcher.SettingWindow" x:Class="Flow.Launcher.SettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:converters="clr-namespace:Flow.Launcher.Converters" xmlns:converters="clr-namespace:Flow.Launcher.Converters"
xmlns:core="clr-namespace:Flow.Launcher.Core.Resource;assembly=Flow.Launcher.Core" xmlns:core="clr-namespace:Flow.Launcher.Core.Resource;assembly=Flow.Launcher.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -67,6 +68,20 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style x:Key="SwitchFocusVisualStyleKey">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle
Margin="-8,-4,-8,-4"
RadiusX="5"
RadiusY="5"
Stroke="{DynamicResource Color05B}"
StrokeThickness="2" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingGrid" TargetType="ItemsControl"> <Style x:Key="SettingGrid" TargetType="ItemsControl">
<Setter Property="Focusable" Value="False" /> <Setter Property="Focusable" Value="False" />
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
@ -137,6 +152,7 @@
<Setter Property="Grid.Column" Value="2" /> <Setter Property="Grid.Column" Value="2" />
<Setter Property="FocusVisualMargin" Value="5" /> <Setter Property="FocusVisualMargin" Value="5" />
<Setter Property="Width" Value="Auto" /> <Setter Property="Width" Value="Auto" />
<Setter Property="FocusVisualStyle" Value="{DynamicResource SwitchFocusVisualStyleKey}" />
<Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Right" /> <Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="OffContent" Value="{DynamicResource disable}" /> <Setter Property="OffContent" Value="{DynamicResource disable}" />
@ -447,7 +463,10 @@
IsItemsHost="true" IsItemsHost="true"
LastChildFill="False" /> LastChildFill="False" />
<Border Grid.Column="1"> <Border Grid.Column="1">
<ContentPresenter Grid.Column="1" ContentSource="SelectedContent" /> <ContentPresenter
x:Name="PART_SelectedContentHost"
Grid.Column="1"
ContentSource="SelectedContent" />
</Border> </Border>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
@ -693,7 +712,7 @@
</ItemsControl> </ItemsControl>
</Border> </Border>
<Border <Border
Margin="0,30,0,0" Margin="0,30,0,0"
Padding="0" Padding="0"
Style="{DynamicResource SettingGroupBox}"> Style="{DynamicResource SettingGroupBox}">
@ -720,7 +739,7 @@
<ComboBox <ComboBox
x:Name="SelectScreen" x:Name="SelectScreen"
MinWidth="160" MinWidth="160"
Margin="0,0,18,0" Margin="0,0,18,0"
VerticalAlignment="Center" VerticalAlignment="Center"
FontSize="14" FontSize="14"
ItemsSource="{Binding ScreenNumbers}" ItemsSource="{Binding ScreenNumbers}"
@ -738,14 +757,12 @@
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
<TextBlock Style="{StaticResource Glyph}"> <TextBlock Style="{StaticResource Glyph}">
&#xe7f4; &#xe7f4;
</TextBlock> </TextBlock>
</ItemsControl> </ItemsControl>
</Border> </Border>
<Separator <Separator Width="Auto" BorderThickness="1">
Width="Auto"
BorderThickness="1">
<Separator.Style> <Separator.Style>
<Style BasedOn="{StaticResource SettingSeparatorStyle}" TargetType="Separator"> <Style BasedOn="{StaticResource SettingSeparatorStyle}" TargetType="Separator">
<Setter Property="Visibility" Value="Visible" /> <Setter Property="Visibility" Value="Visible" />
@ -758,9 +775,7 @@
</Separator.Style> </Separator.Style>
</Separator> </Separator>
<Border <Border Margin="0" BorderThickness="0">
Margin="0"
BorderThickness="0">
<Border.Style> <Border.Style>
<Style BasedOn="{StaticResource SettingGroupBox}" TargetType="Border"> <Style BasedOn="{StaticResource SettingGroupBox}" TargetType="Border">
<Setter Property="Visibility" Value="Visible" /> <Setter Property="Visibility" Value="Visible" />
@ -786,8 +801,7 @@
FontSize="14" FontSize="14"
ItemsSource="{Binding SearchWindowAligns}" ItemsSource="{Binding SearchWindowAligns}"
SelectedValue="{Binding Settings.SearchWindowAlign}" SelectedValue="{Binding Settings.SearchWindowAlign}"
SelectedValuePath="Value"> SelectedValuePath="Value" />
</ComboBox>
<StackPanel Margin="0,0,18,0" Orientation="Horizontal"> <StackPanel Margin="0,0,18,0" Orientation="Horizontal">
<StackPanel.Style> <StackPanel.Style>
<Style TargetType="StackPanel"> <Style TargetType="StackPanel">
@ -817,7 +831,7 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<TextBlock Style="{StaticResource Glyph}"> <TextBlock Style="{StaticResource Glyph}">
&#xe7f4; &#xe7f4;
</TextBlock> </TextBlock>
</ItemsControl> </ItemsControl>
</Border> </Border>
@ -2446,8 +2460,7 @@
FontSize="14" FontSize="14"
ItemsSource="{Binding AnimationSpeeds}" ItemsSource="{Binding AnimationSpeeds}"
SelectedValue="{Binding Settings.AnimationSpeed}" SelectedValue="{Binding Settings.AnimationSpeed}"
SelectedValuePath="Value"> SelectedValuePath="Value" />
</ComboBox>
<StackPanel Margin="0,0,18,0" Orientation="Horizontal"> <StackPanel Margin="0,0,18,0" Orientation="Horizontal">
<StackPanel.Style> <StackPanel.Style>
<Style TargetType="StackPanel"> <Style TargetType="StackPanel">
@ -2474,27 +2487,81 @@
</StackPanel> </StackPanel>
</Border> </Border>
<Border <Border
Margin="0" Margin="0,12,0,12"
BorderThickness="0" Padding="0"
CornerRadius="5"
Style="{DynamicResource SettingGroupBox}"> Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}"> <StackPanel Orientation="Vertical">
<StackPanel Style="{StaticResource TextPanel}"> <Border
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource SoundEffect}" /> Margin="0"
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource SoundEffectTip}" /> BorderThickness="0"
</StackPanel> Style="{DynamicResource SettingGroupBox}">
<ui:ToggleSwitch <ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource SoundEffect}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource SoundEffectTip}" />
</StackPanel>
<ui:ToggleSwitch
x:Name="SoundEffect"
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
IsOn="{Binding UseSound, Mode=TwoWay}" IsOn="{Binding UseSound, Mode=TwoWay}"
OffContent="{DynamicResource disable}" OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" OnContent="{DynamicResource enable}"
Style="{DynamicResource SideToggleSwitch}" /> Style="{DynamicResource SideToggleSwitch}" />
<TextBlock Style="{StaticResource Glyph}"> <TextBlock Style="{StaticResource Glyph}">
&#xe7f5;
</TextBlock>
</ItemsControl>
</Border>
<Separator
Width="Auto"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<Border Margin="0" BorderThickness="0">
<Border.Style>
<Style BasedOn="{StaticResource SettingGroupBox}" TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SoundEffect, Path=IsOn}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource SoundEffectVolume}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource SoundEffectVolumeTip}" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBlock
Width="Auto"
Margin="0,0,8,2"
VerticalAlignment="Center"
Foreground="{DynamicResource Color05B}"
Text="{Binding ElementName=SoundEffectValue, Path=Value, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Right" />
<Slider
Name="SoundEffectValue"
Width="250"
Margin="0,0,18,0"
VerticalAlignment="Center"
IsMoveToPointEnabled="True"
IsSnapToTickEnabled="True"
Maximum="100"
Minimum="0"
TickFrequency="1"
Value="{Binding SoundEffectVolume, Mode=TwoWay}" />
</StackPanel>
<TextBlock Style="{StaticResource Glyph}">
&#xe994; &#xe994;
</TextBlock> </TextBlock>
</ItemsControl> </ItemsControl>
</Border>
</StackPanel>
</Border> </Border>
<Border Margin="0,12,0,12" Style="{DynamicResource SettingGroupBox}"> <Border Margin="0,12,0,12" Style="{DynamicResource SettingGroupBox}">
@ -2581,63 +2648,39 @@
<StackPanel Margin="5,18,18,10"> <StackPanel Margin="5,18,18,10">
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
Margin="0,5,0,2" Margin="0,5,0,6"
FontSize="30" FontSize="30"
Style="{StaticResource PageTitle}" Style="{StaticResource PageTitle}"
Text="{DynamicResource hotkeys}" Text="{DynamicResource hotkeys}"
TextAlignment="left" /> TextAlignment="left" />
<StackPanel Grid.Row="1"> <StackPanel Grid.Row="1">
<Border Margin="0,8,0,0" Style="{DynamicResource SettingGroupBox}"> <cc:Card
<ItemsControl Style="{StaticResource SettingGrid}"> Title="{DynamicResource flowlauncherHotkey}"
<StackPanel Style="{StaticResource TextPanel}"> Icon="&#xeda7;"
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource flowlauncherHotkey}" /> Sub="{DynamicResource flowlauncherHotkeyToolTip}">
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource flowlauncherHotkeyToolTip}" /> <flowlauncher:HotkeyControl
</StackPanel> ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
<flowlauncher:HotkeyControl DefaultHotkey="Alt+Space"
x:Name="HotkeyControl" Hotkey="{Binding Settings.Hotkey}"
Grid.Row="0" HotkeySettings="{Binding Settings}"
Grid.Column="2" ValidateKeyGesture="True"
Width="300" WindowTitle="{DynamicResource flowlauncherHotkey}" />
Height="35" </cc:Card>
Margin="0,0,0,0"
HorizontalAlignment="Right"
HorizontalContentAlignment="Right"
GotFocus="OnHotkeyControlFocused"
Loaded="OnHotkeyControlLoaded"
LostFocus="OnHotkeyControlFocusLost" />
<TextBlock Style="{StaticResource Glyph}">
&#xeda7;
</TextBlock>
</ItemsControl>
</Border>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="2"> <StackPanel Grid.Row="2">
<Border Margin="0,8,0,0" Style="{DynamicResource SettingGroupBox}"> <cc:Card
<ItemsControl Style="{StaticResource SettingGrid}"> Title="{DynamicResource previewHotkey}"
<StackPanel Style="{StaticResource TextPanel}"> Icon="&#xe8a1;"
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource previewHotkey}" /> Sub="{DynamicResource previewHotkeyToolTip}">
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource previewHotkeyToolTip}" /> <flowlauncher:HotkeyControl
</StackPanel> DefaultHotkey="F1"
<flowlauncher:HotkeyControl Hotkey="{Binding Settings.PreviewHotkey}"
x:Name="PreviewHotkeyControl" HotkeySettings="{Binding Settings}"
Grid.Row="0" ValidateKeyGesture="False"
Grid.Column="2" WindowTitle="{DynamicResource previewHotkey}" />
Width="300" </cc:Card>
Height="35"
Margin="0,0,0,0"
HorizontalAlignment="Right"
HorizontalContentAlignment="Right"
Loaded="OnPreviewHotkeyControlLoaded"
LostFocus="OnPreviewHotkeyControlFocusLost"
ValidateKeyGesture="True" />
<TextBlock Style="{StaticResource Glyph}">
&#xe8a1;
</TextBlock>
</ItemsControl>
</Border>
</StackPanel> </StackPanel>
<Border <Border
@ -2697,163 +2740,374 @@
</StackPanel> </StackPanel>
</Border> </Border>
<TextBlock <StackPanel Grid.Row="2">
Grid.Row="4" <cc:ExCard
Margin="0,10,12,10" Title="{DynamicResource hotkeyPresets}"
Padding="0,12,0,0" Margin="0,14,0,0"
VerticalAlignment="Center" Icon="&#xf0e2;"
FontSize="14" Sub="{DynamicResource hotkeyPresetsToolTip}">
Foreground="{DynamicResource Color05B}" <StackPanel>
Text="{DynamicResource customQueryHotkey}" /> <cc:Card
<ListView Title="{DynamicResource OpenContainFolderHotkey}"
Grid.Row="5" Icon="&#xe8b7;"
MinHeight="160" Type="Inside">
Margin="0,0,0,0" <cc:HotkeyDisplay Keys="Ctrl+Enter" />
Background="{DynamicResource Color02B}" </cc:Card>
BorderBrush="DarkGray" <cc:Card
BorderThickness="1" Title="{DynamicResource RunAsAdminHotkey}"
ItemsSource="{Binding Settings.CustomPluginHotkeys}" Icon="&#xe7ef;"
SelectedItem="{Binding SelectedCustomPluginHotkey}" Type="Inside">
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}"> <cc:HotkeyDisplay Keys="Ctrl+Shift+Enter" />
<ListView.View> </cc:Card>
<GridView> <cc:Card
<GridViewColumn Width="180" Header="{DynamicResource hotkey}"> Title="{DynamicResource ToggleHistoryHotkey}"
<GridViewColumn.CellTemplate> Icon="&#xf738;"
<DataTemplate DataType="userSettings:CustomPluginHotkey"> Type="Inside">
<TextBlock Text="{Binding Hotkey}" /> <cc:HotkeyDisplay Keys="Ctrl+H" />
</DataTemplate> </cc:Card>
</GridViewColumn.CellTemplate> <cc:Card
</GridViewColumn> Title="{DynamicResource CopyFilePathHotkey}"
<GridViewColumn Width="430" Header="{DynamicResource customQuery}"> Icon="&#xe8c8;"
<GridViewColumn.CellTemplate> Type="Inside">
<DataTemplate DataType="userSettings:CustomPluginHotkey"> <cc:HotkeyDisplay Keys="Ctrl+Shift+C" />
<TextBlock Text="{Binding ActionKeyword}" /> </cc:Card>
</DataTemplate> <cc:Card
</GridViewColumn.CellTemplate> Title="{DynamicResource OpenContextMenuHotkey}"
</GridViewColumn> Icon="&#xede3;"
</GridView> Type="Inside">
</ListView.View> <flowlauncher:HotkeyControl
</ListView> DefaultHotkey="Ctrl+I"
<StackPanel Hotkey="{Binding Settings.OpenContextMenuHotkey}"
Grid.Row="6" HotkeySettings="{Binding Settings}"
Margin="0" ValidateKeyGesture="False" />
HorizontalAlignment="Right" </cc:Card>
VerticalAlignment="Top" <cc:Card
Orientation="Horizontal"> Title="{DynamicResource OpenContextMenuHotkey}"
<Button Icon="&#xede3;"
MinWidth="100" Type="Inside">
Margin="10" <cc:HotkeyDisplay Keys="Shift+Enter" />
Click="OnDeleteCustomHotkeyClick" </cc:Card>
Content="{DynamicResource delete}" />
<Button <cc:Card
MinWidth="100" Title="{DynamicResource SettingWindowHotkey}"
Margin="10" Icon="&#xe713;"
Click="OnEditCustomHotkeyClick" Type="Inside">
Content="{DynamicResource edit}" /> <flowlauncher:HotkeyControl
<Button DefaultHotkey="Ctrl+I"
MinWidth="100" Hotkey="{Binding Settings.SettingWindowHotkey}"
Margin="10,10,0,10" HotkeySettings="{Binding Settings}"
Click="OnAddCustomHotkeyClick" ValidateKeyGesture="False" />
Content="{DynamicResource add}" /> </cc:Card>
<cc:Card
Title="{DynamicResource ToggleGameModeHotkey}"
Icon="&#xe7fc;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+F12" />
</cc:Card>
<cc:Card
Title="{DynamicResource RequeryHotkey}"
Icon="&#xe72c;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+R" />
</cc:Card>
<cc:Card
Title="{DynamicResource ReloadPluginHotkey}"
Icon="&#xe72c;"
Sub="{DynamicResource ReloadPluginHotkeyToolTip}"
Type="Inside">
<cc:HotkeyDisplay Keys="F5" />
</cc:Card>
<cc:Card
Title="{DynamicResource SelectPrevPageHotkey}"
Icon="&#xf0ad;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectPrevPageHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource SelectNextPageHotkey}"
Icon="&#xf0ae;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectNextPageHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource QuickWidthHotkey}"
Icon="&#xe7ea;"
Type="Inside">
<StackPanel Orientation="Horizontal">
<cc:HotkeyDisplay Keys="Ctrl+[" />
<cc:HotkeyDisplay Margin="4,0,0,0" Keys="Ctrl+]" />
</StackPanel>
</cc:Card>
<cc:Card
Title="{DynamicResource QuickHeightHotkey}"
Icon="&#xe7eb;"
Type="Inside">
<StackPanel Orientation="Horizontal">
<cc:HotkeyDisplay Keys="Ctrl+Plus" />
<cc:HotkeyDisplay Margin="4,0,0,0" Keys="Ctrl+Minus" />
</StackPanel>
</cc:Card>
</StackPanel>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource autoCompleteHotkey}"
Margin="0,14,0,0"
Icon="&#xe893;"
Sub="{DynamicResource autoCompleteHotkeyToolTip}">
<cc:ExCard.SideContent>
<flowlauncher:HotkeyControl
DefaultHotkey="Ctrl+Tab"
Hotkey="{Binding Settings.AutoCompleteHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource autoCompleteHotkey}"
Sub="{DynamicResource AdditionalHotkeyToolTip}"
Type="InsideFit">
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.AutoCompleteHotkey2}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource SelectPrevItemHotkey}"
Margin="0,4,0,0"
Icon="&#xe74a;">
<cc:ExCard.SideContent>
<flowlauncher:HotkeyControl
DefaultHotkey="Shift+Tab"
Hotkey="{Binding Settings.SelectPrevItemHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource SelectPrevItemHotkey}"
Sub="{DynamicResource AdditionalHotkeyToolTip}"
Type="InsideFit">
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectPrevItemHotkey2}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource SelectNextItemHotkey}"
Margin="0,4,0,0"
Icon="&#xe74b;">
<cc:ExCard.SideContent>
<flowlauncher:HotkeyControl
DefaultHotkey="Tab"
Hotkey="{Binding Settings.SelectNextItemHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource SelectNextItemHotkey}"
Sub="{DynamicResource AdditionalHotkeyToolTip}"
Type="InsideFit">
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectNextItemHotkey2}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource customQueryHotkey}"
Margin="0,20,0,0"
Icon="&#xf26c;">
<StackPanel Margin="0,0,0,0">
<Separator
Width="Auto"
Margin="0"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<StackPanel Margin="18,18,18,0">
<ListView
Grid.Row="5"
MinHeight="160"
Margin="0,0,0,0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding Settings.CustomPluginHotkeys}"
SelectedItem="{Binding SelectedCustomPluginHotkey}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource hotkey}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="userSettings:CustomPluginHotkey">
<TextBlock Text="{Binding Hotkey}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource customQuery}">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="userSettings:CustomPluginHotkey">
<TextBlock Text="{Binding ActionKeyword}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel
Grid.Row="6"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Orientation="Horizontal">
<Button
MinWidth="100"
Margin="10"
Click="OnDeleteCustomHotkeyClick"
Content="{DynamicResource delete}" />
<Button
MinWidth="100"
Margin="10"
Click="OnEditCustomHotkeyClick"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
Margin="10,10,0,10"
Click="OnAddCustomHotkeyClick"
Content="{DynamicResource add}" />
</StackPanel>
</StackPanel>
</StackPanel>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource customQueryShortcut}"
Margin="0,4,0,0"
Icon="&#xf26b;">
<StackPanel>
<Separator
Width="Auto"
Margin="0"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<StackPanel Margin="18,12,18,0">
<ListView
Grid.Row="8"
MinHeight="160"
Margin="0,6,0,0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding CustomShortcuts}"
SelectedItem="{Binding SelectedCustomShortcut}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource customShortcutExpansion}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel
Grid.Row="9"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Orientation="Horizontal">
<Button
MinWidth="100"
Margin="10"
Click="OnDeleteCustomShortCutClick"
Content="{DynamicResource delete}" />
<Button
MinWidth="100"
Margin="10"
Click="OnEditCustomShortCutClick"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
Margin="10,10,0,10"
Click="OnAddCustomShortCutClick"
Content="{DynamicResource add}" />
</StackPanel>
</StackPanel>
</StackPanel>
</cc:ExCard>
<cc:ExCard
Title="{DynamicResource builtinShortcuts}"
Margin="0,4,0,14"
Icon="&#xf158;">
<StackPanel>
<Separator
Width="Auto"
Margin="0"
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
<StackPanel Margin="16,8,16,0">
<ListView
Grid.Row="11"
MinHeight="160"
Margin="0,6,0,16"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding BuiltinShortcuts}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource builtinShortcutDescription}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description, Converter={StaticResource TranslationConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</StackPanel>
</cc:ExCard>
</StackPanel> </StackPanel>
<TextBlock
Grid.Row="7"
Margin="0,0,12,2"
Padding="0,12,0,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color05B}"
Text="{DynamicResource customQueryShortcut}" />
<ListView
Name="customShortcutView"
Grid.Row="8"
MinHeight="160"
Margin="0,6,0,0"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding CustomShortcuts}"
SelectedItem="{Binding SelectedCustomShortcut}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource customShortcutExpansion}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel
Grid.Row="9"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Orientation="Horizontal">
<Button
MinWidth="100"
Margin="10"
Click="OnDeleteCustomShortCutClick"
Content="{DynamicResource delete}" />
<Button
MinWidth="100"
Margin="10"
Click="OnEditCustomShortCutClick"
Content="{DynamicResource edit}" />
<Button
MinWidth="100"
Margin="10,10,0,10"
Click="OnAddCustomShortCutClick"
Content="{DynamicResource add}" />
</StackPanel>
<TextBlock
Grid.Row="10"
Margin="0,0,12,2"
Padding="0,12,0,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color05B}"
Text="{DynamicResource builtinShortcuts}" />
<ListView
Grid.Row="11"
MinHeight="160"
Margin="0,6,0,20"
Background="{DynamicResource Color02B}"
BorderBrush="DarkGray"
BorderThickness="1"
ItemsSource="{Binding BuiltinShortcuts}"
Style="{StaticResource {x:Static GridView.GridViewStyleKey}}">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="{DynamicResource customShortcut}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="430" Header="{DynamicResource builtinShortcutDescription}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description, Converter={StaticResource TranslationConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel> </StackPanel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>

View file

@ -17,6 +17,7 @@ using System.Windows.Forms;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Navigation; using System.Windows.Navigation;
using NHotkey;
using Button = System.Windows.Controls.Button; using Button = System.Windows.Controls.Button;
using Control = System.Windows.Controls.Control; using Control = System.Windows.Controls.Control;
using KeyEventArgs = System.Windows.Input.KeyEventArgs; using KeyEventArgs = System.Windows.Input.KeyEventArgs;
@ -110,41 +111,9 @@ namespace Flow.Launcher
#region Hotkey #region Hotkey
private void OnHotkeyControlLoaded(object sender, RoutedEventArgs e) private void OnToggleHotkey(object sender, HotkeyEventArgs e)
{ {
_ = HotkeyControl.SetHotkeyAsync(viewModel.Settings.Hotkey, false); HotKeyMapper.OnToggleHotkey(sender, e);
}
private void OnHotkeyControlFocused(object sender, RoutedEventArgs e)
{
HotKeyMapper.RemoveHotkey(settings.Hotkey);
}
private void OnHotkeyControlFocusLost(object sender, RoutedEventArgs e)
{
if (HotkeyControl.CurrentHotkeyAvailable)
{
HotKeyMapper.SetHotkey(HotkeyControl.CurrentHotkey, HotKeyMapper.OnToggleHotkey);
HotKeyMapper.RemoveHotkey(settings.Hotkey);
settings.Hotkey = HotkeyControl.CurrentHotkey.ToString();
}
else
{
HotKeyMapper.SetHotkey(new HotkeyModel(settings.Hotkey), HotKeyMapper.OnToggleHotkey);
}
}
private void OnPreviewHotkeyControlLoaded(object sender, RoutedEventArgs e)
{
_ = PreviewHotkeyControl.SetHotkeyAsync(settings.PreviewHotkey, false);
}
private void OnPreviewHotkeyControlFocusLost(object sender, RoutedEventArgs e)
{
if (PreviewHotkeyControl.CurrentHotkeyAvailable)
{
settings.PreviewHotkey = PreviewHotkeyControl.CurrentHotkey.ToString();
}
} }
private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e) private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e)
@ -391,7 +360,7 @@ namespace Flow.Launcher
{ {
if (viewModel.EditSelectedCustomShortcut()) if (viewModel.EditSelectedCustomShortcut())
{ {
customShortcutView.Items.Refresh(); //customShortcutView.Items.Refresh(); Should Fix
} }
} }

View file

@ -80,6 +80,36 @@ namespace Flow.Launcher.ViewModel
case nameof(Settings.PreviewHotkey): case nameof(Settings.PreviewHotkey):
OnPropertyChanged(nameof(PreviewHotkey)); OnPropertyChanged(nameof(PreviewHotkey));
break; break;
case nameof(Settings.AutoCompleteHotkey):
OnPropertyChanged(nameof(AutoCompleteHotkey));
break;
case nameof(Settings.AutoCompleteHotkey2):
OnPropertyChanged(nameof(AutoCompleteHotkey2));
break;
case nameof(Settings.SelectNextItemHotkey):
OnPropertyChanged(nameof(SelectNextItemHotkey));
break;
case nameof(Settings.SelectNextItemHotkey2):
OnPropertyChanged(nameof(SelectNextItemHotkey2));
break;
case nameof(Settings.SelectPrevItemHotkey):
OnPropertyChanged(nameof(SelectPrevItemHotkey));
break;
case nameof(Settings.SelectPrevItemHotkey2):
OnPropertyChanged(nameof(SelectPrevItemHotkey2));
break;
case nameof(Settings.SelectNextPageHotkey):
OnPropertyChanged(nameof(SelectNextPageHotkey));
break;
case nameof(Settings.SelectPrevPageHotkey):
OnPropertyChanged(nameof(SelectPrevPageHotkey));
break;
case nameof(Settings.OpenContextMenuHotkey):
OnPropertyChanged(nameof(OpenContextMenuHotkey));
break;
case nameof(Settings.SettingWindowHotkey):
OnPropertyChanged(nameof(SettingWindowHotkey));
break;
} }
}; };
@ -210,7 +240,7 @@ namespace Flow.Launcher.ViewModel
} }
[RelayCommand] [RelayCommand]
private void ReQuery() public void ReQuery()
{ {
if (SelectedIsFromQueryResults()) if (SelectedIsFromQueryResults())
{ {
@ -218,6 +248,14 @@ namespace Flow.Launcher.ViewModel
} }
} }
public void ReQuery(bool reselect)
{
if (SelectedIsFromQueryResults())
{
QueryResults(isReQuery: true, reSelect: reselect);
}
}
[RelayCommand] [RelayCommand]
private void LoadContextMenu() private void LoadContextMenu()
{ {
@ -334,6 +372,13 @@ namespace Flow.Launcher.ViewModel
SelectedResults.SelectFirstResult(); SelectedResults.SelectFirstResult();
} }
[RelayCommand]
private void SelectLastResult()
{
SelectedResults.SelectLastResult();
}
[RelayCommand] [RelayCommand]
private void SelectPrevPage() private void SelectPrevPage()
{ {
@ -619,25 +664,33 @@ namespace Flow.Launcher.ViewModel
public string OpenResultCommandModifiers => Settings.OpenResultModifiers; public string OpenResultCommandModifiers => Settings.OpenResultModifiers;
public string PreviewHotkey public string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotkey)
{ {
get try
{ {
// TODO try to patch issue #1755 var converter = new KeyGestureConverter();
// Added in v1.14.0, remove after v1.16.0. var key = (KeyGesture)converter.ConvertFromString(hotkey);
try
{
var converter = new KeyGestureConverter();
var key = (KeyGesture)converter.ConvertFromString(Settings.PreviewHotkey);
}
catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException)
{
Settings.PreviewHotkey = "F1";
}
return Settings.PreviewHotkey;
} }
catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException)
{
return defaultHotkey;
}
return hotkey;
} }
public string PreviewHotkey => VerifyOrSetDefaultHotkey(Settings.PreviewHotkey, "F1");
public string AutoCompleteHotkey => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey, "Ctrl+Tab");
public string AutoCompleteHotkey2 => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey2, "");
public string SelectNextItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey, "Tab");
public string SelectNextItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey2, "");
public string SelectPrevItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey, "Shift+Tab");
public string SelectPrevItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey2, "");
public string SelectNextPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextPageHotkey, "");
public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, "");
public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O");
public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I");
public string Image => Constant.QueryTextBoxIconImagePath; public string Image => Constant.QueryTextBoxIconImagePath;
@ -680,20 +733,7 @@ namespace Flow.Launcher.ViewModel
List<Result> results; List<Result> results;
if (selected == lastContextMenuResult) if (selected == lastContextMenuResult)
{ {
// Use copy to keep the original results unchanged results = lastContextMenuResults;
results = lastContextMenuResults.ConvertAll(result => new Result
{
Title = result.Title,
SubTitle = result.SubTitle,
IcoPath = result.IcoPath,
PluginDirectory = result.PluginDirectory,
Action = result.Action,
ContextData = result.ContextData,
Glyph = result.Glyph,
OriginQuery = result.OriginQuery,
Score = result.Score,
AsyncAction = result.AsyncAction,
});
} }
else else
{ {
@ -707,7 +747,7 @@ namespace Flow.Launcher.ViewModel
if (!string.IsNullOrEmpty(query)) if (!string.IsNullOrEmpty(query))
{ {
var filtered = results.Where var filtered = results.Select(x => x.Clone()).Where
( (
r => r =>
{ {
@ -775,7 +815,7 @@ namespace Flow.Launcher.ViewModel
private readonly IReadOnlyList<Result> _emptyResult = new List<Result>(); private readonly IReadOnlyList<Result> _emptyResult = new List<Result>();
private async void QueryResults(bool isReQuery = false) private async void QueryResults(bool isReQuery = false, bool reSelect = true)
{ {
_updateSource?.Cancel(); _updateSource?.Cancel();
@ -850,7 +890,7 @@ namespace Flow.Launcher.ViewModel
var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
{ {
false => QueryTask(plugin), false => QueryTask(plugin, reSelect),
true => Task.CompletedTask true => Task.CompletedTask
}).ToArray(); }).ToArray();
@ -878,7 +918,7 @@ namespace Flow.Launcher.ViewModel
} }
// Local function // Local function
async Task QueryTask(PluginPair plugin) async Task QueryTask(PluginPair plugin, bool reSelect = true)
{ {
// Since it is wrapped within a ThreadPool Thread, the synchronous context is null // Since it is wrapped within a ThreadPool Thread, the synchronous context is null
// Task.Yield will force it to run in ThreadPool // Task.Yield will force it to run in ThreadPool
@ -892,7 +932,7 @@ namespace Flow.Launcher.ViewModel
results ??= _emptyResult; results ??= _emptyResult;
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query,
currentCancellationToken))) currentCancellationToken, reSelect)))
{ {
Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
} }
@ -1133,7 +1173,7 @@ namespace Flow.Launcher.ViewModel
/// <summary> /// <summary>
/// To avoid deadlock, this method should not called from main thread /// To avoid deadlock, this method should not called from main thread
/// </summary> /// </summary>
public void UpdateResultView(IEnumerable<ResultsForUpdate> resultsForUpdates) public void UpdateResultView(ICollection<ResultsForUpdate> resultsForUpdates)
{ {
if (!resultsForUpdates.Any()) if (!resultsForUpdates.Any())
return; return;
@ -1172,7 +1212,10 @@ namespace Flow.Launcher.ViewModel
} }
} }
Results.AddResults(resultsForUpdates, token); // it should be the same for all results
bool reSelect = resultsForUpdates.First().ReSelectFirstResult;
Results.AddResults(resultsForUpdates, token, reSelect);
} }
#endregion #endregion

View file

@ -4,23 +4,13 @@ using System.Threading;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
{ {
public struct ResultsForUpdate public record struct ResultsForUpdate(
IReadOnlyList<Result> Results,
PluginMetadata Metadata,
Query Query,
CancellationToken Token,
bool ReSelectFirstResult = true)
{ {
public IReadOnlyList<Result> Results { get; } public string ID { get; } = Metadata.ID;
public PluginMetadata Metadata { get; }
public string ID { get; }
public Query Query { get; }
public CancellationToken Token { get; }
public ResultsForUpdate(IReadOnlyList<Result> results, PluginMetadata metadata, Query query, CancellationToken token)
{
Results = results;
Metadata = metadata;
Query = query;
Token = token;
ID = metadata.ID;
}
} }
} }

View file

@ -117,6 +117,11 @@ namespace Flow.Launcher.ViewModel
SelectedIndex = NewIndex(0); SelectedIndex = NewIndex(0);
} }
public void SelectLastResult()
{
SelectedIndex = NewIndex(Results.Count - 1);
}
public void Clear() public void Clear()
{ {
lock (_collectionLock) lock (_collectionLock)
@ -147,23 +152,23 @@ namespace Flow.Launcher.ViewModel
/// <summary> /// <summary>
/// To avoid deadlock, this method should not called from main thread /// To avoid deadlock, this method should not called from main thread
/// </summary> /// </summary>
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token) public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token, bool reselect = true)
{ {
var newResults = NewResults(resultsForUpdates); var newResults = NewResults(resultsForUpdates);
if (token.IsCancellationRequested) if (token.IsCancellationRequested)
return; return;
UpdateResults(newResults, token); UpdateResults(newResults, token, reselect);
} }
private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default) private void UpdateResults(List<ResultViewModel> newResults, CancellationToken token = default, bool reselect = true)
{ {
lock (_collectionLock) lock (_collectionLock)
{ {
// update UI in one run, so it can avoid UI flickering // update UI in one run, so it can avoid UI flickering
Results.Update(newResults, token); Results.Update(newResults, token);
if (Results.Any()) if (reselect && Results.Any())
SelectedItem = Results[0]; SelectedItem = Results[0];
} }

View file

@ -22,6 +22,8 @@ using Flow.Launcher.Plugin.SharedModels;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices;
using Flow.Launcher.Infrastructure.Hotkey;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
{ {
@ -63,9 +65,17 @@ namespace Flow.Launcher.ViewModel
case nameof(Settings.PreviewHotkey): case nameof(Settings.PreviewHotkey):
OnPropertyChanged(nameof(AlwaysPreviewToolTip)); OnPropertyChanged(nameof(AlwaysPreviewToolTip));
break; break;
case nameof(Settings.SoundVolume):
OnPropertyChanged(nameof(SoundEffectVolume));
break;
} }
}; };
}
[RelayCommand]
public void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
} }
public Settings Settings { get; set; } public Settings Settings { get; set; }
@ -107,13 +117,15 @@ namespace Flow.Launcher.ViewModel
} }
catch (Exception e) catch (Exception e)
{ {
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message); Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
e.Message);
} }
} }
} }
// This is only required to set at startup. When portable mode enabled/disabled a restart is always required // This is only required to set at startup. When portable mode enabled/disabled a restart is always required
private bool _portableMode = DataLocation.PortableDataLocationInUse(); private bool _portableMode = DataLocation.PortableDataLocationInUse();
public bool PortableMode public bool PortableMode
{ {
get => _portableMode; get => _portableMode;
@ -182,6 +194,7 @@ namespace Flow.Launcher.ViewModel
} }
private List<LastQueryMode> _lastQueryModes = new List<LastQueryMode>(); private List<LastQueryMode> _lastQueryModes = new List<LastQueryMode>();
public List<LastQueryMode> LastQueryModes public List<LastQueryMode> LastQueryModes
{ {
get get
@ -190,6 +203,7 @@ namespace Flow.Launcher.ViewModel
{ {
_lastQueryModes = InitLastQueryModes(); _lastQueryModes = InitLastQueryModes();
} }
return _lastQueryModes; return _lastQueryModes;
} }
} }
@ -197,17 +211,16 @@ namespace Flow.Launcher.ViewModel
private List<LastQueryMode> InitLastQueryModes() private List<LastQueryMode> InitLastQueryModes()
{ {
var modes = new List<LastQueryMode>(); var modes = new List<LastQueryMode>();
var enums = (Infrastructure.UserSettings.LastQueryMode[])Enum.GetValues(typeof(Infrastructure.UserSettings.LastQueryMode)); var enums = (Infrastructure.UserSettings.LastQueryMode[])Enum.GetValues(
typeof(Infrastructure.UserSettings.LastQueryMode));
foreach (var e in enums) foreach (var e in enums)
{ {
var key = $"LastQuery{e}"; var key = $"LastQuery{e}";
var display = _translater.GetTranslation(key); var display = _translater.GetTranslation(key);
var m = new LastQueryMode var m = new LastQueryMode { Display = display, Value = e, };
{
Display = display, Value = e,
};
modes.Add(m); modes.Add(m);
} }
return modes; return modes;
} }
@ -264,15 +277,15 @@ namespace Flow.Launcher.ViewModel
public List<string> OpenResultModifiersList => new List<string> public List<string> OpenResultModifiersList => new List<string>
{ {
KeyConstant.Alt, KeyConstant.Alt, KeyConstant.Ctrl, $"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
KeyConstant.Ctrl,
$"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
}; };
private Internationalization _translater => InternationalizationManager.Instance; private Internationalization _translater => InternationalizationManager.Instance;
public List<Language> Languages => _translater.LoadAvailableLanguages(); public List<Language> Languages => _translater.LoadAvailableLanguages();
public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16); public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16);
public string AlwaysPreviewToolTip => string.Format(_translater.GetTranslation("AlwaysPreviewToolTip"), Settings.PreviewHotkey); public string AlwaysPreviewToolTip =>
string.Format(_translater.GetTranslation("AlwaysPreviewToolTip"), Settings.PreviewHotkey);
public string TestProxy() public string TestProxy()
{ {
@ -282,6 +295,7 @@ namespace Flow.Launcher.ViewModel
{ {
return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty"); return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty");
} }
if (Settings.Proxy.Port <= 0) if (Settings.Proxy.Port <= 0)
{ {
return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty"); return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty");
@ -300,6 +314,7 @@ namespace Flow.Launcher.ViewModel
Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password) Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password)
}; };
} }
try try
{ {
var response = (HttpWebResponse)request.GetResponse(); var response = (HttpWebResponse)request.GetResponse();
@ -330,10 +345,7 @@ namespace Flow.Launcher.ViewModel
get => PluginManager.AllPlugins get => PluginManager.AllPlugins
.OrderBy(x => x.Metadata.Disabled) .OrderBy(x => x.Metadata.Disabled)
.ThenBy(y => y.Metadata.Name) .ThenBy(y => y.Metadata.Name)
.Select(p => new PluginViewModel .Select(p => new PluginViewModel { PluginPair = p })
{
PluginPair = p
})
.ToList(); .ToList();
} }
@ -380,8 +392,7 @@ namespace Flow.Launcher.ViewModel
await PluginsManifest.UpdateManifestAsync(); await PluginsManifest.UpdateManifestAsync();
OnPropertyChanged(nameof(ExternalPlugins)); OnPropertyChanged(nameof(ExternalPlugins));
} }
internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0) internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0)
{ {
@ -455,12 +466,10 @@ namespace Flow.Launcher.ViewModel
{ {
var key = $"ColorScheme{e}"; var key = $"ColorScheme{e}";
var display = _translater.GetTranslation(key); var display = _translater.GetTranslation(key);
var m = new ColorScheme var m = new ColorScheme { Display = display, Value = e, };
{
Display = display, Value = e,
};
modes.Add(m); modes.Add(m);
} }
return modes; return modes;
} }
} }
@ -481,13 +490,10 @@ namespace Flow.Launcher.ViewModel
{ {
var key = $"SearchWindowScreen{e}"; var key = $"SearchWindowScreen{e}";
var display = _translater.GetTranslation(key); var display = _translater.GetTranslation(key);
var m = new SearchWindowScreen var m = new SearchWindowScreen { Display = display, Value = e, };
{
Display = display,
Value = e,
};
modes.Add(m); modes.Add(m);
} }
return modes; return modes;
} }
} }
@ -508,12 +514,10 @@ namespace Flow.Launcher.ViewModel
{ {
var key = $"SearchWindowAlign{e}"; var key = $"SearchWindowAlign{e}";
var display = _translater.GetTranslation(key); var display = _translater.GetTranslation(key);
var m = new SearchWindowAlign var m = new SearchWindowAlign { Display = display, Value = e, };
{
Display = display, Value = e,
};
modes.Add(m); modes.Add(m);
} }
return modes; return modes;
} }
} }
@ -528,6 +532,7 @@ namespace Flow.Launcher.ViewModel
{ {
screenNumbers.Add(i); screenNumbers.Add(i);
} }
return screenNumbers; return screenNumbers;
} }
} }
@ -614,13 +619,10 @@ namespace Flow.Launcher.ViewModel
{ {
var key = $"AnimationSpeed{e}"; var key = $"AnimationSpeed{e}";
var display = _translater.GetTranslation(key); var display = _translater.GetTranslation(key);
var m = new AnimationSpeed var m = new AnimationSpeed { Display = display, Value = e, };
{
Display = display,
Value = e,
};
speeds.Add(m); speeds.Add(m);
} }
return speeds; return speeds;
} }
} }
@ -631,6 +633,12 @@ namespace Flow.Launcher.ViewModel
set => Settings.UseSound = value; set => Settings.UseSound = value;
} }
public double SoundEffectVolume
{
get => Settings.SoundVolume;
set => Settings.SoundVolume = value;
}
public bool UseClock public bool UseClock
{ {
get => Settings.UseClock; get => Settings.UseClock;
@ -681,10 +689,7 @@ namespace Flow.Launcher.ViewModel
bitmap.DecodePixelWidth = 800; bitmap.DecodePixelWidth = 800;
bitmap.DecodePixelHeight = 600; bitmap.DecodePixelHeight = 600;
bitmap.EndInit(); bitmap.EndInit();
var brush = new ImageBrush(bitmap) var brush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
{
Stretch = Stretch.UniformToFill
};
return brush; return brush;
} }
else else
@ -705,26 +710,36 @@ namespace Flow.Launcher.ViewModel
new Result new Result
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"), Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"), SubTitle =
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png") InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"),
IcoPath =
Path.Combine(Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png")
}, },
new Result new Result
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"), Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"), SubTitle =
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png") InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"),
IcoPath =
Path.Combine(Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
}, },
new Result new Result
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"), Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"), SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"),
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 new Result
{ {
Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"), Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"),
SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"), SubTitle =
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png") InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"),
IcoPath = Path.Combine(Constant.ProgramDirectory,
@"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
} }
}; };
var vm = new ResultsViewModel(Settings); var vm = new ResultsViewModel(Settings);
@ -880,6 +895,7 @@ namespace Flow.Launcher.ViewModel
SelectedCustomShortcut = item; SelectedCustomShortcut = item;
return true; return true;
} }
return false; return false;
} }
@ -908,6 +924,7 @@ namespace Flow.Launcher.ViewModel
public string Documentation => Constant.Documentation; public string Documentation => Constant.Documentation;
public string Docs => Constant.Docs; public string Docs => Constant.Docs;
public string Github => Constant.GitHub; public string Github => Constant.GitHub;
public string Version public string Version
{ {
get get
@ -922,7 +939,9 @@ namespace Flow.Launcher.ViewModel
} }
} }
} }
public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
public string ActivatedTimes =>
string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
public string CheckLogFolder public string CheckLogFolder
{ {
@ -930,7 +949,8 @@ namespace Flow.Launcher.ViewModel
{ {
var logFiles = GetLogFiles(); var logFiles = GetLogFiles();
long size = logFiles.Sum(file => file.Length); long size = logFiles.Sum(file => file.Length);
return string.Format("{0} ({1})", _translater.GetTranslation("clearlogfolder"), BytesToReadableString(size)); return string.Format("{0} ({1})", _translater.GetTranslation("clearlogfolder"),
BytesToReadableString(size));
} }
} }
@ -967,10 +987,7 @@ namespace Flow.Launcher.ViewModel
internal static string BytesToReadableString(long bytes) internal static string BytesToReadableString(long bytes)
{ {
const int scale = 1024; const int scale = 1024;
string[] orders = new string[] string[] orders = new string[] { "GB", "MB", "KB", "B" };
{
"GB", "MB", "KB", "B"
};
long max = (long)Math.Pow(scale, orders.Length - 1); long max = (long)Math.Pow(scale, orders.Length - 1);
foreach (string order in orders) foreach (string order in orders)
@ -980,6 +997,7 @@ namespace Flow.Launcher.ViewModel
max /= scale; max /= scale;
} }
return "0 B"; return "0 B";
} }

View file

@ -10,6 +10,10 @@
Title="{DynamicResource Welcome_Page1_Title}" Title="{DynamicResource Welcome_Page1_Title}"
Width="550" Width="550"
Height="650" Height="650"
MinWidth="550"
MinHeight="650"
MaxWidth="550"
MaxHeight="650"
Activated="OnActivated" Activated="OnActivated"
Background="{DynamicResource Color00B}" Background="{DynamicResource Color00B}"
Foreground="{DynamicResource PopupTextColor}" Foreground="{DynamicResource PopupTextColor}"

View file

@ -3,23 +3,22 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
{
public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{
public override List<Bookmark> GetBookmarks()
{
return LoadChromeBookmarks();
}
private List<Bookmark> LoadChromeBookmarks() public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{ {
var bookmarks = new List<Bookmark>(); public override List<Bookmark> GetBookmarks()
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); {
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome")); return LoadChromeBookmarks();
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium"));
return bookmarks;
}
} }
}
private List<Bookmark> LoadChromeBookmarks()
{
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium"));
return bookmarks;
}
}

View file

@ -4,91 +4,90 @@ using System.IO;
using System.Text.Json; using System.Text.Json;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
public abstract class ChromiumBookmarkLoader : IBookmarkLoader
{ {
public abstract class ChromiumBookmarkLoader : IBookmarkLoader public abstract List<Bookmark> GetBookmarks();
protected List<Bookmark> LoadBookmarks(string browserDataPath, string name)
{ {
public abstract List<Bookmark> GetBookmarks(); var bookmarks = new List<Bookmark>();
if (!Directory.Exists(browserDataPath)) return bookmarks;
var paths = Directory.GetDirectories(browserDataPath);
protected List<Bookmark> LoadBookmarks(string browserDataPath, string name) foreach (var profile in paths)
{ {
var bookmarks = new List<Bookmark>(); var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!Directory.Exists(browserDataPath)) return bookmarks; if (!File.Exists(bookmarkPath))
var paths = Directory.GetDirectories(browserDataPath); continue;
foreach (var profile in paths) Main.RegisterBookmarkFile(bookmarkPath);
{
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
Main.RegisterBookmarkFile(bookmarkPath); var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
}
var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); return bookmarks;
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); }
}
protected List<Bookmark> LoadBookmarksFromFile(string path, string source)
{
var bookmarks = new List<Bookmark>();
if (!File.Exists(path))
return bookmarks; return bookmarks;
}
protected List<Bookmark> LoadBookmarksFromFile(string path, string source) using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
{ if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))
var bookmarks = new List<Bookmark>();
if (!File.Exists(path))
return bookmarks;
using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))
return bookmarks;
EnumerateRoot(rootElement, bookmarks, source);
return bookmarks; return bookmarks;
} EnumerateRoot(rootElement, bookmarks, source);
return bookmarks;
}
private void EnumerateRoot(JsonElement rootElement, ICollection<Bookmark> bookmarks, string source) private void EnumerateRoot(JsonElement rootElement, ICollection<Bookmark> bookmarks, string source)
{
foreach (var folder in rootElement.EnumerateObject())
{ {
foreach (var folder in rootElement.EnumerateObject()) if (folder.Value.ValueKind != JsonValueKind.Object)
{ continue;
if (folder.Value.ValueKind != JsonValueKind.Object)
continue;
// Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details. // Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details.
// If various exceptions start to build up here consider splitting this Loader into multiple separate ones. // If various exceptions start to build up here consider splitting this Loader into multiple separate ones.
if (folder.Name == "custom_root") if (folder.Name == "custom_root")
EnumerateRoot(folder.Value, bookmarks, source); EnumerateRoot(folder.Value, bookmarks, source);
else else
EnumerateFolderBookmark(folder.Value, bookmarks, source); EnumerateFolderBookmark(folder.Value, bookmarks, source);
}
}
private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Bookmark> bookmarks,
string source)
{
if (!folderElement.TryGetProperty("children", out var childrenElement))
return;
foreach (var subElement in childrenElement.EnumerateArray())
{
if (subElement.TryGetProperty("type", out var type))
{
switch (type.GetString())
{
case "folder":
case "workspace": // Edge Workspace
EnumerateFolderBookmark(subElement, bookmarks, source);
break;
default:
bookmarks.Add(new Bookmark(
subElement.GetProperty("name").GetString(),
subElement.GetProperty("url").GetString(),
source));
break;
}
} }
} else
private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Bookmark> bookmarks,
string source)
{
if (!folderElement.TryGetProperty("children", out var childrenElement))
return;
foreach (var subElement in childrenElement.EnumerateArray())
{ {
if (subElement.TryGetProperty("type", out var type)) Log.Error(
{ $"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}");
switch (type.GetString())
{
case "folder":
case "workspace": // Edge Workspace
EnumerateFolderBookmark(subElement, bookmarks, source);
break;
default:
bookmarks.Add(new Bookmark(
subElement.GetProperty("name").GetString(),
subElement.GetProperty("url").GetString(),
source));
break;
}
}
else
{
Log.Error(
$"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}");
}
} }
} }
} }

View file

@ -4,56 +4,55 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Models;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.Plugin.BrowserBookmark.Commands namespace Flow.Launcher.Plugin.BrowserBookmark.Commands;
internal static class BookmarkLoader
{ {
internal static class BookmarkLoader internal static MatchResult MatchProgram(Bookmark bookmark, string queryString)
{ {
internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) var match = StringMatcher.FuzzySearch(queryString, bookmark.Name);
{ if (match.IsSearchPrecisionScoreMet())
var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); return match;
if (match.IsSearchPrecisionScoreMet())
return match;
return StringMatcher.FuzzySearch(queryString, bookmark.Url); return StringMatcher.FuzzySearch(queryString, bookmark.Url);
}
internal static List<Bookmark> LoadAllBookmarks(Settings setting)
{
var allBookmarks = new List<Bookmark>();
if (setting.LoadChromeBookmark)
{
// Add Chrome bookmarks
var chromeBookmarks = new ChromeBookmarkLoader();
allBookmarks.AddRange(chromeBookmarks.GetBookmarks());
} }
internal static List<Bookmark> LoadAllBookmarks(Settings setting) if (setting.LoadFirefoxBookmark)
{ {
var allBookmarks = new List<Bookmark>(); // Add Firefox bookmarks
var mozBookmarks = new FirefoxBookmarkLoader();
if (setting.LoadChromeBookmark) allBookmarks.AddRange(mozBookmarks.GetBookmarks());
{
// Add Chrome bookmarks
var chromeBookmarks = new ChromeBookmarkLoader();
allBookmarks.AddRange(chromeBookmarks.GetBookmarks());
}
if (setting.LoadFirefoxBookmark)
{
// Add Firefox bookmarks
var mozBookmarks = new FirefoxBookmarkLoader();
allBookmarks.AddRange(mozBookmarks.GetBookmarks());
}
if (setting.LoadEdgeBookmark)
{
// Add Edge (Chromium) bookmarks
var edgeBookmarks = new EdgeBookmarkLoader();
allBookmarks.AddRange(edgeBookmarks.GetBookmarks());
}
foreach (var browser in setting.CustomChromiumBrowsers)
{
IBookmarkLoader loader = browser.BrowserType switch
{
BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser),
BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser),
_ => new CustomChromiumBookmarkLoader(browser),
};
allBookmarks.AddRange(loader.GetBookmarks());
}
return allBookmarks.Distinct().ToList();
} }
if (setting.LoadEdgeBookmark)
{
// Add Edge (Chromium) bookmarks
var edgeBookmarks = new EdgeBookmarkLoader();
allBookmarks.AddRange(edgeBookmarks.GetBookmarks());
}
foreach (var browser in setting.CustomChromiumBrowsers)
{
IBookmarkLoader loader = browser.BrowserType switch
{
BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser),
BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser),
_ => new CustomChromiumBookmarkLoader(browser),
};
allBookmarks.AddRange(loader.GetBookmarks());
}
return allBookmarks.Distinct().ToList();
} }
} }

View file

@ -1,19 +1,18 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System.Collections.Generic; using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
{
public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader
{
public CustomChromiumBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
public string BrowserDataPath { get; init; }
public string BookmarkFilePath { get; init; }
public string BrowserName { get; init; }
public override List<Bookmark> GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName); public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader
{
public CustomChromiumBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
} }
} public string BrowserDataPath { get; init; }
public string BookmarkFilePath { get; init; }
public string BrowserName { get; init; }
public override List<Bookmark> GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName);
}

View file

@ -2,26 +2,25 @@
using System.IO; using System.IO;
using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Models;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
{
public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
{
public CustomFirefoxBookmarkLoader(CustomBrowser browser)
{
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
/// <summary>
/// Path to places.sqlite
/// </summary>
public string BrowserDataPath { get; init; }
public string BrowserName { get; init; }
public override List<Bookmark> GetBookmarks() public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
{ {
return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite")); public CustomFirefoxBookmarkLoader(CustomBrowser browser)
} {
BrowserName = browser.Name;
BrowserDataPath = browser.DataDirectoryPath;
}
/// <summary>
/// Path to places.sqlite
/// </summary>
public string BrowserDataPath { get; init; }
public string BrowserName { get; init; }
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite"));
} }
} }

View file

@ -3,21 +3,20 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
{
public class EdgeBookmarkLoader : ChromiumBookmarkLoader
{
private List<Bookmark> LoadEdgeBookmarks()
{
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary"));
return bookmarks; public class EdgeBookmarkLoader : ChromiumBookmarkLoader
} {
private List<Bookmark> LoadEdgeBookmarks()
public override List<Bookmark> GetBookmarks() => LoadEdgeBookmarks(); {
var bookmarks = new List<Bookmark>();
var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev"));
bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary"));
return bookmarks;
} }
}
public override List<Bookmark> GetBookmarks() => LoadEdgeBookmarks();
}

View file

@ -5,140 +5,133 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
{ {
public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader public abstract List<Bookmark> GetBookmarks();
{
public abstract List<Bookmark> GetBookmarks();
private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title private const string QueryAllBookmarks = """
FROM moz_places SELECT moz_places.url, moz_bookmarks.title
INNER JOIN moz_bookmarks ON ( FROM moz_places
INNER JOIN moz_bookmarks ON (
moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id
) )
ORDER BY moz_places.visit_count DESC ORDER BY moz_places.visit_count DESC
"; """;
private const string dbPathFormat = "Data Source ={0}"; private const string DbPathFormat = "Data Source ={0}";
protected static List<Bookmark> GetBookmarksFromPath(string placesPath) protected static List<Bookmark> GetBookmarksFromPath(string placesPath)
{ {
// Return empty list if the places.sqlite file cannot be found // Return empty list if the places.sqlite file cannot be found
if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath))
return new List<Bookmark>(); return new List<Bookmark>();
var bookmarkList = new List<Bookmark>(); Main.RegisterBookmarkFile(placesPath);
Main.RegisterBookmarkFile(placesPath); // create the connection string and init the connection
string dbPath = string.Format(DbPathFormat, placesPath);
using var dbConnection = new SqliteConnection(dbPath);
// Open connection to the database file and execute the query
dbConnection.Open();
var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader();
// create the connection string and init the connection // return results in List<Bookmark> format
string dbPath = string.Format(dbPathFormat, placesPath); return reader
using var dbConnection = new SqliteConnection(dbPath); .Select(
// Open connection to the database file and execute the query x => new Bookmark(
dbConnection.Open(); x["title"] is DBNull ? string.Empty : x["title"].ToString(),
var reader = new SqliteCommand(queryAllBookmarks, dbConnection).ExecuteReader(); x["url"].ToString()
)
// return results in List<Bookmark> format )
bookmarkList = reader.Select( .ToList();
x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), }
x["url"].ToString()) }
).ToList();
return bookmarkList;
} public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
{
/// <summary>
/// Searches the places.sqlite db and returns all bookmarks
/// </summary>
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(PlacesPath);
} }
/// <summary>
public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase /// Path to places.sqlite
/// </summary>
private string PlacesPath
{ {
/// <summary> get
/// Searches the places.sqlite db and returns all bookmarks
/// </summary>
public override List<Bookmark> GetBookmarks()
{ {
return GetBookmarksFromPath(PlacesPath); var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
} var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
/// <summary>
/// Path to places.sqlite
/// </summary>
private string PlacesPath
{
get
{
var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
if (!File.Exists(profileIni)) if (!File.Exists(profileIni))
return string.Empty; return string.Empty;
// get firefox default profile directory from profiles.ini // get firefox default profile directory from profiles.ini
string ini; using var sReader = new StreamReader(profileIni);
using (var sReader = new StreamReader(profileIni)) var ini = sReader.ReadToEnd();
{
ini = sReader.ReadToEnd();
}
/* /*
Current profiles.ini structure example as of Firefox version 69.0.1 Current profiles.ini structure example as of Firefox version 69.0.1
[Install736426B0AF4A39CB]
Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
Locked=1
[Profile2] [Install736426B0AF4A39CB]
Name=newblahprofile Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
IsRelative=0 Locked=1
Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
[Profile1] [Profile2]
Name=default Name=newblahprofile
IsRelative=1 IsRelative=0
Path=Profiles/cydum7q4.default Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
Default=1
[Profile0] [Profile1]
Name=default-release Name=default
IsRelative=1 IsRelative=1
Path=Profiles/7789f565.default-release Path=Profiles/cydum7q4.default
Default=1
[General] [Profile0]
StartWithLastProfile=1 Name=default-release
Version=2 IsRelative=1
*/ Path=Profiles/7789f565.default-release
var lines = ini.Split(new string[] [General]
{ StartWithLastProfile=1
"\r\n" Version=2
}, StringSplitOptions.None).ToList(); */
var lines = ini.Split("\r\n").ToList();
var defaultProfileFolderNameRaw = lines.Where(x => x.Contains("Default=") && x != "Default=1").FirstOrDefault() ?? string.Empty; var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty;
if (string.IsNullOrEmpty(defaultProfileFolderNameRaw)) if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
return string.Empty; return string.Empty;
var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last(); var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last();
var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path=" + defaultProfileFolderName); var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName);
// Seen in the example above, the IsRelative attribute is always above the Path attribute // Seen in the example above, the IsRelative attribute is always above the Path attribute
var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1]; var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1];
return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0 return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
? defaultProfileFolderName + @"\places.sqlite" ? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite"; : Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
} }
} }
} }
public static class Extensions public static class Extensions
{ {
public static IEnumerable<T> Select<T>(this SqliteDataReader reader, Func<SqliteDataReader, T> projection) public static IEnumerable<T> Select<T>(this SqliteDataReader reader, Func<SqliteDataReader, T> projection)
{ {
while (reader.Read()) while (reader.Read())
{ {
yield return projection(reader); yield return projection(reader);
}
} }
} }
} }

View file

@ -56,7 +56,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.2" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,10 +1,9 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System.Collections.Generic; using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
public interface IBookmarkLoader
{ {
public interface IBookmarkLoader public List<Bookmark> GetBookmarks();
{ }
public List<Bookmark> GetBookmarks();
}
}

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin.BrowserBookmark.Commands; using Flow.Launcher.Plugin.BrowserBookmark.Commands;
@ -12,233 +11,234 @@ using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using System.Threading;
namespace Flow.Launcher.Plugin.BrowserBookmark namespace Flow.Launcher.Plugin.BrowserBookmark;
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable
{ {
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable private static PluginInitContext _context;
private static List<Bookmark> _cachedBookmarks = new List<Bookmark>();
private static Settings _settings;
private static bool _initialized = false;
public void Init(PluginInitContext context)
{ {
private static PluginInitContext context; _context = context;
private static List<Bookmark> cachedBookmarks = new List<Bookmark>(); _settings = context.API.LoadSettingJsonStorage<Settings>();
private static Settings _settings; LoadBookmarksIfEnabled();
}
private static bool initialized = false; private static void LoadBookmarksIfEnabled()
{
public void Init(PluginInitContext context) if (_context.CurrentPluginMetadata.Disabled)
{ {
Main.context = context; // Don't load or monitor files if disabled
return;
}
_settings = context.API.LoadSettingJsonStorage<Settings>(); _cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings);
_ = MonitorRefreshQueueAsync();
_initialized = true;
}
public List<Result> Query(Query query)
{
// For when the plugin being previously disabled and is now re-enabled
if (!_initialized)
{
LoadBookmarksIfEnabled(); LoadBookmarksIfEnabled();
} }
private static void LoadBookmarksIfEnabled() string param = query.Search.TrimStart();
// Should top results be returned? (true if no search parameters have been passed)
var topResults = string.IsNullOrEmpty(param);
if (!topResults)
{ {
if (context.CurrentPluginMetadata.Disabled) // Since we mixed chrome and firefox bookmarks, we should order them again
{ return _cachedBookmarks
// Don't load or monitor files if disabled .Select(
return; c => new Result
}
cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings);
_ = MonitorRefreshQueueAsync();
initialized = true;
}
public List<Result> Query(Query query)
{
// For when the plugin being previously disabled and is now renabled
if (!initialized)
{
LoadBookmarksIfEnabled();
}
string param = query.Search.TrimStart();
// Should top results be returned? (true if no search parameters have been passed)
var topResults = string.IsNullOrEmpty(param);
if (!topResults)
{
// Since we mixed chrome and firefox bookmarks, we should order them again
var returnList = cachedBookmarks.Select(c => new Result()
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = BookmarkLoader.MatchProgram(c, param).Score,
Action = _ =>
{ {
context.API.OpenUrl(c.Url); Title = c.Name,
SubTitle = c.Url,
return true; IcoPath = @"Images\bookmark.png",
}, Score = BookmarkLoader.MatchProgram(c, param).Score,
ContextData = new BookmarkAttributes Action = _ =>
{
Url = c.Url
}
}).Where(r => r.Score > 0);
return returnList.ToList();
}
else
{
return cachedBookmarks.Select(c => new Result()
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = 5,
Action = _ =>
{
context.API.OpenUrl(c.Url);
return true;
},
ContextData = new BookmarkAttributes
{
Url = c.Url
}
}).ToList();
}
}
private static Channel<byte> refreshQueue = Channel.CreateBounded<byte>(1);
private static SemaphoreSlim fileMonitorSemaphore = new(1, 1);
private static async Task MonitorRefreshQueueAsync()
{
if (fileMonitorSemaphore.CurrentCount < 1)
{
return;
}
await fileMonitorSemaphore.WaitAsync();
var reader = refreshQueue.Reader;
while (await reader.WaitToReadAsync())
{
if (reader.TryRead(out _))
{
ReloadAllBookmarks(false);
}
}
fileMonitorSemaphore.Release();
}
private static readonly List<FileSystemWatcher> Watchers = new();
internal static void RegisterBookmarkFile(string path)
{
var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory) || !File.Exists(path))
{
return;
}
if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase)))
{
return;
}
var watcher = new FileSystemWatcher(directory!);
watcher.Filter = Path.GetFileName(path);
watcher.NotifyFilter = NotifyFilters.FileName |
NotifyFilters.LastWrite |
NotifyFilters.Size;
watcher.Changed += static (_, _) =>
{
refreshQueue.Writer.TryWrite(default);
};
watcher.Renamed += static (_, _) =>
{
refreshQueue.Writer.TryWrite(default);
};
watcher.EnableRaisingEvents = true;
Watchers.Add(watcher);
}
public void ReloadData()
{
ReloadAllBookmarks();
}
public static void ReloadAllBookmarks(bool disposeFileWatchers = true)
{
cachedBookmarks.Clear();
if (disposeFileWatchers)
DisposeFileWatchers();
LoadBookmarksIfEnabled();
}
public string GetTranslatedPluginTitle()
{
return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description");
}
public Control CreateSettingPanel()
{
return new SettingsControl(_settings);
}
public List<Result> LoadContextMenus(Result selectedResult)
{
return new List<Result>()
{
new Result
{
Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
Action = _ =>
{
try
{ {
context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url); _context.API.OpenUrl(c.Url);
return true; return true;
} },
catch (Exception e) ContextData = new BookmarkAttributes { Url = c.Url }
}
)
.Where(r => r.Score > 0)
.ToList();
}
else
{
return _cachedBookmarks
.Select(
c => new Result
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = 5,
Action = _ =>
{ {
var message = "Failed to set url in clipboard"; _context.API.OpenUrl(c.Url);
Log.Exception("Main", message, e, "LoadContextMenus"); return true;
},
context.API.ShowMsg(message); ContextData = new BookmarkAttributes { Url = c.Url }
}
return false; )
} .ToList();
},
IcoPath = "Images\\copylink.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
}
};
}
internal class BookmarkAttributes
{
internal string Url { get; set; }
}
public void Dispose()
{
DisposeFileWatchers();
}
private static void DisposeFileWatchers()
{
foreach (var watcher in Watchers)
{
watcher.Dispose();
}
Watchers.Clear();
} }
} }
private static Channel<byte> _refreshQueue = Channel.CreateBounded<byte>(1);
private static SemaphoreSlim _fileMonitorSemaphore = new(1, 1);
private static async Task MonitorRefreshQueueAsync()
{
if (_fileMonitorSemaphore.CurrentCount < 1)
{
return;
}
await _fileMonitorSemaphore.WaitAsync();
var reader = _refreshQueue.Reader;
while (await reader.WaitToReadAsync())
{
if (reader.TryRead(out _))
{
ReloadAllBookmarks(false);
}
}
_fileMonitorSemaphore.Release();
}
private static readonly List<FileSystemWatcher> Watchers = new();
internal static void RegisterBookmarkFile(string path)
{
var directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory) || !File.Exists(path))
{
return;
}
if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase)))
{
return;
}
var watcher = new FileSystemWatcher(directory!);
watcher.Filter = Path.GetFileName(path);
watcher.NotifyFilter = NotifyFilters.FileName |
NotifyFilters.LastWrite |
NotifyFilters.Size;
watcher.Changed += static (_, _) =>
{
_refreshQueue.Writer.TryWrite(default);
};
watcher.Renamed += static (_, _) =>
{
_refreshQueue.Writer.TryWrite(default);
};
watcher.EnableRaisingEvents = true;
Watchers.Add(watcher);
}
public void ReloadData()
{
ReloadAllBookmarks();
}
public static void ReloadAllBookmarks(bool disposeFileWatchers = true)
{
_cachedBookmarks.Clear();
if (disposeFileWatchers)
DisposeFileWatchers();
LoadBookmarksIfEnabled();
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description");
}
public Control CreateSettingPanel()
{
return new SettingsControl(_settings);
}
public List<Result> LoadContextMenus(Result selectedResult)
{
return new List<Result>()
{
new Result
{
Title = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
SubTitle = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
Action = _ =>
{
try
{
_context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url);
return true;
}
catch (Exception e)
{
var message = "Failed to set url in clipboard";
Log.Exception("Main", message, e, "LoadContextMenus");
_context.API.ShowMsg(message);
return false;
}
},
IcoPath = @"Images\copylink.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
}
};
}
internal class BookmarkAttributes
{
internal string Url { get; set; }
}
public void Dispose()
{
DisposeFileWatchers();
}
private static void DisposeFileWatchers()
{
foreach (var watcher in Watchers)
{
watcher.Dispose();
}
Watchers.Clear();
}
} }

View file

@ -1,22 +1,21 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Flow.Launcher.Plugin.BrowserBookmark.Models namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
// Source may be important in the future
public record Bookmark(string Name, string Url, string Source = "")
{ {
// Source may be important in the future public override int GetHashCode()
public record Bookmark(string Name, string Url, string Source = "")
{ {
public override int GetHashCode() var hashName = Name?.GetHashCode() ?? 0;
{ var hashUrl = Url?.GetHashCode() ?? 0;
var hashName = Name?.GetHashCode() ?? 0; return hashName ^ hashUrl;
var hashUrl = Url?.GetHashCode() ?? 0;
return hashName ^ hashUrl;
}
public virtual bool Equals(Bookmark other)
{
return other != null && Name == other.Name && Url == other.Url;
}
public List<CustomBrowser> CustomBrowsers { get; set; }= new();
} }
}
public virtual bool Equals(Bookmark other)
{
return other != null && Name == other.Name && Url == other.Url;
}
public List<CustomBrowser> CustomBrowsers { get; set; } = new();
}

View file

@ -1,45 +1,44 @@
namespace Flow.Launcher.Plugin.BrowserBookmark.Models namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
{
public class CustomBrowser : BaseModel
{
private string _name;
private string _dataDirectoryPath;
private BrowserType browserType = BrowserType.Chromium;
public string Name public class CustomBrowser : BaseModel
{
private string _name;
private string _dataDirectoryPath;
private BrowserType _browserType = BrowserType.Chromium;
public string Name
{
get => _name;
set
{ {
get => _name; _name = value;
set OnPropertyChanged();
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
public string DataDirectoryPath
{
get => _dataDirectoryPath;
set
{
_dataDirectoryPath = value;
OnPropertyChanged(nameof(DataDirectoryPath));
}
}
public BrowserType BrowserType
{
get => browserType;
set
{
browserType = value;
OnPropertyChanged(nameof(BrowserType));
}
} }
} }
public enum BrowserType public string DataDirectoryPath
{ {
Chromium, get => _dataDirectoryPath;
Firefox, set
{
_dataDirectoryPath = value;
OnPropertyChanged();
}
}
public BrowserType BrowserType
{
get => _browserType;
set
{
_browserType = value;
OnPropertyChanged();
}
} }
} }
public enum BrowserType
{
Chromium,
Firefox,
}

View file

@ -1,17 +1,16 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
namespace Flow.Launcher.Plugin.BrowserBookmark.Models namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
public class Settings : BaseModel
{ {
public class Settings : BaseModel public bool OpenInNewBrowserWindow { get; set; } = true;
{
public bool OpenInNewBrowserWindow { get; set; } = true;
public string BrowserPath { get; set; } public string BrowserPath { get; set; }
public bool LoadChromeBookmark { get; set; } = true; public bool LoadChromeBookmark { get; set; } = true;
public bool LoadFirefoxBookmark { get; set; } = true; public bool LoadFirefoxBookmark { get; set; } = true;
public bool LoadEdgeBookmark { get; set; } = true; public bool LoadEdgeBookmark { get; set; } = true;
public ObservableCollection<CustomBrowser> CustomChromiumBrowsers { get; set; } = new(); public ObservableCollection<CustomBrowser> CustomChromiumBrowsers { get; set; } = new();
} }
}

Some files were not shown because too many files have changed in this diff Show more