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
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.symbols = constant_fields
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_using_directive_placement = outside_namespace:silent
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_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent

View file

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

View file

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

View file

@ -65,7 +65,7 @@ jobs:
- name: Publish BrowserBookmark
if: steps.changes.outputs.browserbookmark == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.BrowserBookmark"
files: "Flow.Launcher.Plugin.BrowserBookmark.zip"
@ -92,7 +92,7 @@ jobs:
- name: Publish Calculator
if: steps.changes.outputs.calculator == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Calculator"
files: "Flow.Launcher.Plugin.Calculator.zip"
@ -119,7 +119,7 @@ jobs:
- name: Publish Explorer
if: steps.changes.outputs.explorer == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Explorer"
files: "Flow.Launcher.Plugin.Explorer.zip"
@ -146,7 +146,7 @@ jobs:
- name: Publish PluginIndicator
if: steps.changes.outputs.pluginindicator == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginIndicator"
files: "Flow.Launcher.Plugin.PluginIndicator.zip"
@ -173,7 +173,7 @@ jobs:
- name: Publish PluginsManager
if: steps.changes.outputs.pluginsmanager == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginsManager"
files: "Flow.Launcher.Plugin.PluginsManager.zip"
@ -200,7 +200,7 @@ jobs:
- name: Publish ProcessKiller
if: steps.changes.outputs.processkiller == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller"
files: "Flow.Launcher.Plugin.ProcessKiller.zip"
@ -227,7 +227,7 @@ jobs:
- name: Publish Program
if: steps.changes.outputs.program == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Program"
files: "Flow.Launcher.Plugin.Program.zip"
@ -254,7 +254,7 @@ jobs:
- name: Publish Shell
if: steps.changes.outputs.shell == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Shell"
files: "Flow.Launcher.Plugin.Shell.zip"
@ -281,7 +281,7 @@ jobs:
- name: Publish Sys
if: steps.changes.outputs.sys == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Sys"
files: "Flow.Launcher.Plugin.Sys.zip"
@ -308,7 +308,7 @@ jobs:
- name: Publish Url
if: steps.changes.outputs.url == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.Url"
files: "Flow.Launcher.Plugin.Url.zip"
@ -335,7 +335,7 @@ jobs:
- name: Publish WebSearch
if: steps.changes.outputs.websearch == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.WebSearch"
files: "Flow.Launcher.Plugin.WebSearch.zip"
@ -362,7 +362,7 @@ jobs:
- name: Publish WindowsSettings
if: steps.changes.outputs.windowssettings == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
repository: "Flow-Launcher/Flow.Launcher.Plugin.WindowsSettings"
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
{
@ -27,6 +27,8 @@ namespace Flow.Launcher.Core.Resource
public static Language Turkish = new Language("tr", "Türkçe");
public static Language Czech = new Language("cs", "čeština");
public static Language Arabic = new Language("ar", "اللغة العربية");
public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt");
public static List<Language> GetAvailableLanguages()
{
@ -54,7 +56,8 @@ namespace Flow.Launcher.Core.Resource
Slovak,
Turkish,
Czech,
Arabic
Arabic,
Vietnamese
};
return languages;
}

View file

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

View file

@ -54,7 +54,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</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="NLog" 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
{
public class HotkeyModel
public record struct HotkeyModel
{
public bool Alt { 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>
{
{Key.Space, "Space"},
{Key.Oem3, "~"}
{ Key.Space, "Space" }, { Key.Oem3, "~" }
};
public ModifierKeys ModifierKeys
@ -30,18 +29,22 @@ namespace Flow.Launcher.Infrastructure.Hotkey
{
modifierKeys |= ModifierKeys.Alt;
}
if (Shift)
{
modifierKeys |= ModifierKeys.Shift;
}
if (Win)
{
modifierKeys |= ModifierKeys.Windows;
}
if (Ctrl)
{
modifierKeys |= ModifierKeys.Control;
}
return modifierKeys;
}
}
@ -66,31 +69,37 @@ namespace Flow.Launcher.Infrastructure.Hotkey
{
return;
}
List<string> keys = hotkeyString.Replace(" ", "").Split('+').ToList();
if (keys.Contains("Alt"))
{
Alt = true;
keys.Remove("Alt");
}
if (keys.Contains("Shift"))
{
Shift = true;
keys.Remove("Shift");
}
if (keys.Contains("Win"))
{
Win = true;
keys.Remove("Win");
}
if (keys.Contains("Ctrl"))
{
Ctrl = true;
keys.Remove("Ctrl");
}
if (keys.Count == 1)
{
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)
{
CharKey = specialSymbolPair.Value.Key;
@ -103,7 +112,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey
}
catch (ArgumentException)
{
}
}
}
@ -111,33 +119,39 @@ namespace Flow.Launcher.Infrastructure.Hotkey
public override string ToString()
{
List<string> keys = new List<string>();
if (Ctrl)
return string.Join(" + ", EnumerateDisplayKeys());
}
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)
{
keys.Add(specialSymbolDictionary.ContainsKey(CharKey)
? specialSymbolDictionary[CharKey]
: CharKey.ToString());
yield return specialSymbolDictionary.TryGetValue(CharKey, out var value)
? value
: CharKey.ToString();
}
return string.Join(" + ", keys);
}
/// <summary>
/// Validate hotkey
/// </summary>
@ -164,11 +178,13 @@ namespace Flow.Launcher.Infrastructure.Hotkey
{
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;
}
}
if (ModifierKeys == ModifierKeys.None)
{
return !IsPrintableCharacter(CharKey);
@ -206,18 +222,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey
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()
{
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.Text.Json.Serialization;
using System.Windows;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class Settings : BaseModel
public class Settings : BaseModel, IHotkeySettings
{
private string language = "en";
private string _theme = Constant.DefaultTheme;
@ -20,6 +21,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool ShowOpenResultHotkey { get; set; } = true;
public double WindowSize { get; set; } = 580;
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
{
@ -54,6 +65,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool UseGlyphIcons { get; set; } = true;
public bool UseAnimation { get; set; } = true;
public bool UseSound { get; set; } = true;
public double SoundVolume { get; set; } = 50;
public bool UseClock { get; set; } = true;
public bool UseDate { get; set; } = false;
public string TimeFormat { get; set; } = "hh:mm tt";
@ -195,17 +208,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
/// <summary>
/// Custom left position on selected monitor
/// </summary>
public double CustomWindowLeft { get; set; } = 0;
/// <summary>
/// Custom top position on selected monitor
/// </summary>
public double CustomWindowTop { get; set; } = 0;
public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; }
@ -217,7 +230,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonIgnore]
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)
};
@ -241,7 +254,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;
[JsonConverter(typeof(JsonStringEnumConverter))]
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
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
@ -276,7 +365,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Light,
Dark
}
public enum SearchWindowScreens
{
RememberLastLaunchLocation,
@ -285,7 +374,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Primary,
Custom
}
public enum SearchWindowAligns
{
Center,

View file

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

View file

@ -291,5 +291,12 @@ namespace Flow.Launcher.Plugin
/// </summary>
/// <returns></returns>
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;
}
/// <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>
/// Additional data associated with this result
/// </summary>

View file

@ -3,39 +3,38 @@ using System.Globalization;
using System.Windows.Data;
using System.Windows.Input;
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
};
}
namespace Flow.Launcher.Converters;
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)
{
return value switch
{
true => InputMethodState.Off,
_ => InputMethodState.DoNotCare
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
throw new NotImplementedException();
}
}
internal class BoolToIMEStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
true => InputMethodState.Off,
_ => 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.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,
(_, null) => Visibility.Collapsed
};
}
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
(true, null) => Visibility.Visible,
(_, null) => Visibility.Collapsed
};
}
public class SplitterConverter : IValueConverter
/* 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)
{
return (value, parameter) switch
{
(true, not 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();
}
public class SplitterConverter : IValueConverter
/* 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)
{
return (value, parameter) switch
{
(true, not 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();
}

View file

@ -7,43 +7,41 @@ using System.Windows.Shapes;
// 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;
}
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;
return DependencyProperty.UnsetValue;
}
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.Windows.Data;
namespace Flow.Launcher.Converters
{
public class DateTimeFormatToNowConverter : IValueConverter
{
namespace Flow.Launcher.Converters;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not string format ? null : DateTime.Now.ToString(format);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public class DateTimeFormatToNowConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not string format ? null : DateTime.Now.ToString(format);
}
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.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(0, 0);
return new Point(d / 2, d / 2);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
return new Point(0, 0);
}
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.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[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();
if (value.Length < 2)
return new Run(string.Empty);
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);
var run = new Run(currentCharacter)
{
Style = ShouldHighlight(highlightData, i) ? highlightStyle : null
};
textBlock.Inlines.Add(run);
}
return textBlock;
Style = ShouldHighlight(highlightData, i) ? highlightStyle : null
};
textBlock.Inlines.Add(run);
}
return textBlock;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue };
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue };
}
private bool ShouldHighlight(List<int> highlightData, int index)
{
return highlightData.Contains(index);
}
private bool ShouldHighlight(List<int> highlightData, int index)
{
return highlightData.Contains(index);
}
}

View file

@ -2,20 +2,19 @@
using System.Globalization;
using System.Windows.Data;
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]");
namespace Flow.Launcher.Converters;
return isIconCircular ? size / 2 : size;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
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 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.Data;
namespace Flow.Launcher.Converters
namespace Flow.Launcher.Converters;
[ValueConversion(typeof(bool), typeof(Visibility))]
public class OpenResultHotkeyVisibilityConverter : IValueConverter
{
[ValueConversion(typeof(bool), typeof(Visibility))]
public class OpenResultHotkeyVisibilityConverter : IValueConverter
private const int MaxVisibleHotkeys = 10;
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)
{
var number = int.MaxValue;
if (value is ListBoxItem listBoxItem
&& ItemsControl.ItemsControlFromItemContainer(listBoxItem) is ListBox listBox)
number = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1;
if (value is ListBoxItem listBoxItem
&& 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();
return number <= MaxVisibleHotkeys ? Visibility.Visible : Visibility.Collapsed;
}
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.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
|| 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
return 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.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
// 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)
)
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;
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.
// When user typed lower case and result title is uppercase, we still want to display suggestion
selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length);
// For AutocompleteQueryCommand.
// When user typed lower case and result title is uppercase, we still want to display suggestion
selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length);
// Check if Text will be larger than our QueryTextBox
Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch);
// TODO: Obsolete warning?
var ft = new FormattedText(queryTextBox.Text, CultureInfo.DefaultThreadCurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, queryTextBox.FontSize, Brushes.Black);
// Check if Text will be larger than our QueryTextBox
Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch);
// TODO: Obsolete warning?
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)
return string.Empty;
return selectedItem.QuerySuggestionText;
}
catch (Exception e)
{
Log.Exception(nameof(QuerySuggestionBoxConverter), "fail to convert text for suggestion box", e);
if (ft.Width + offset > queryTextBox.ActualWidth || queryTextBox.HorizontalOffset != 0)
return string.Empty;
}
return selectedItem.QuerySuggestionText;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
catch (Exception e)
{
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.Input;
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
};
}
namespace Flow.Launcher.Converters;
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.ViewModel;
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);
}
namespace Flow.Launcher.Converters;
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"
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:flowlauncher="clr-namespace:Flow.Launcher"
Title="{DynamicResource customeQueryHotkeyTitle}"
Width="530"
Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
Icon="Images\app.png"
MouseDown="window_MouseDown"
@ -60,94 +62,76 @@
</Grid>
</StackPanel>
<StackPanel Margin="26,0,26,0">
<StackPanel Grid.Row="0" Margin="0,0,0,12">
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource customeQueryHotkeyTitle}"
TextAlignment="Left" />
</StackPanel>
<StackPanel>
<TextBlock
FontSize="14"
Text="{DynamicResource customeQueryHotkeyTips}"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
<Image
Width="478"
Margin="0,20,0,0"
Source="/Images/illustration_01.png" />
</StackPanel>
<TextBlock
Margin="0,0,0,12"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource customeQueryHotkeyTitle}"
TextAlignment="Left" />
<TextBlock
FontSize="14"
Text="{DynamicResource customeQueryHotkeyTips}"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
<Image
Width="478"
Margin="0,20,0,0"
Source="/Images/illustration_01.png" />
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<Grid Width="478">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource hotkey}" />
<StackPanel
Grid.Row="0"
Grid.Column="1"
Orientation="Horizontal">
<flowlauncher:HotkeyControl
x:Name="ctlHotkey"
Grid.Column="1"
Width="200"
Height="36"
Margin="10,0,10,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
HorizontalContentAlignment="Left" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource actionKeyword}" />
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource customQuery}" />
<DockPanel
Grid.Row="1"
Grid.Column="1"
LastChildFill="True">
<Button
x:Name="btnTestActionKeyword"
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>
<Grid Width="478" Margin="0,20,0,0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource hotkey}" />
<flowlauncher:HotkeyControl
x:Name="HotkeyControl"
Grid.Row="0"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="10,0,10,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
HotkeySettings="{Binding Settings}"
DefaultHotkey="" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource customQuery}" />
<TextBox
x:Name="tbAction"
Grid.Row="1"
Grid.Column="1"
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
<Button
x:Name="btnTestActionKeyword"
Grid.Row="1"
Grid.Column="2"
Margin="0,0,10,0"
Padding="10,5,10,5"
Click="BtnTestActionKeyword_OnClick"
Content="{DynamicResource preview}" />
</Grid>
</StackPanel>
</StackPanel>
<Border
@ -174,4 +158,4 @@
</StackPanel>
</Border>
</Grid>
</Window>
</Window>

View file

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

View file

@ -3,57 +3,56 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
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";
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()
get
{
try
{
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)
{
Log.Error("AutoStartup", $"Failed to disable auto-startup: {e}");
throw;
Log.Error("AutoStartup", $"Ignoring non-critical registry error (querying if enabled): {e}");
}
}
internal static void Enable()
return false;
}
}
public static void Disable()
{
try
{
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;
}
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
key?.DeleteValue(Constant.FlowLauncher, false);
}
catch (Exception e)
{
Log.Error("AutoStartup", $"Failed to disable 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.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)
{
[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))
{
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);
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
if (ret1 == 0)
{
WindowInteropHelper helper = new WindowInteropHelper(window);
int val = 2;
int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4);
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;
}
Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 };
int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
return ret2 == 0;
}
catch (Exception)
else
{
// Probably dwmapi.dll not found (incompatible OS)
return false;
}
}
catch (Exception)
{
// Probably dwmapi.dll not found (incompatible OS)
return false;
}
}
}
}

View file

@ -2,68 +2,67 @@
using System.IO;
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;
public DataWebRequest(Uri uri)
{
m_uri = uri;
}
public override WebResponse GetResponse()
{
return new DataWebResponse(m_uri);
}
_uri = uri;
}
class DataWebResponse : WebResponse
public override WebResponse GetResponse()
{
private readonly string m_contentType;
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);
return new DataWebResponse(_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.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 reportWindow = new ReportWindow(e);
reportWindow.Show();
}
var logger = LogManager.GetLogger("UnHandledException");
logger.Fatal(ExceptionFormatter.FormatExcpetion(e));
var reportWindow = new ReportWindow(e);
reportWindow.Show();
}
public static void UnhandledExceptionHandle(object sender, UnhandledExceptionEventArgs e)
{
//handle non-ui thread exceptions
Report((Exception)e.ExceptionObject);
}
public static void UnhandledExceptionHandle(object sender, UnhandledExceptionEventArgs e)
{
//handle non-ui thread exceptions
Report((Exception)e.ExceptionObject);
}
public static void DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
//handle ui thread exceptions
Report(e.Exception);
//prevent application exist, so the user can copy prompted error info
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 void DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
//handle ui thread exceptions
Report(e.Exception);
//prevent application exist, so the user can copy prompted error info
e.Handled = true;
}
public static string DependenciesInfo()
{
var info = $"\nPython Path: {Constant.PythonPath}\nNode Path: {Constant.NodePath}";
return info;
}
public static string RuntimeInfo()
{
var 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 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;
private static MainViewModel mainViewModel;
_mainViewModel = mainVM;
_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;
settings = mainViewModel.Settings;
SetHotkey(settings.Hotkey, OnToggleHotkey);
LoadCustomPluginHotkey();
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
}
internal static void OnToggleHotkey(object sender, HotkeyEventArgs args)
catch (Exception)
{
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
{
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;
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;
}
}

View file

@ -2,27 +2,26 @@
using System.Linq;
using System.Windows;
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();
namespace Flow.Launcher.Helper;
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;
namespace Flow.Launcher.Helper
{
public static class SyntaxSugars
{
public static TResult CallOrRescueDefault<TResult>(Func<TResult> callback)
{
return CallOrRescueDefault(callback, default(TResult));
}
namespace Flow.Launcher.Helper;
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();
}
catch
{
return def;
}
return callback();
}
catch
{
return def;
}
}
}

View file

@ -5,44 +5,43 @@ using System.Text;
using System.Windows.Media;
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)]
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;
var wallpaper = new StringBuilder(MAX_PATH);
SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallpaper, 0);
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);
SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallpaper, 0);
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 != null && result is string)
try
{
var parts = strResult.Trim().Split(new[] {' '}, 3).Select(byte.Parse).ToList();
return Color.FromRgb(parts[0], parts[1], parts[2]);
}
catch
{
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 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
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
{
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)]
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", SetLastError = true)]
internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
internal static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
internal static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
internal static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
internal static extern IntPtr GetShellWindow();
[DllImport("user32.dll")]
internal static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
internal static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
[DllImport("user32.dll")]
internal static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
[DllImport("user32.DLL")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
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_WINTAB = "Flip3D";
const string WINDOW_CLASS_PROGMAN = "Progman";
const string WINDOW_CLASS_WORKERW = "WorkerW";
const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass";
const string WINDOW_CLASS_WINTAB = "Flip3D";
const string WINDOW_CLASS_PROGMAN = "Progman";
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;
}
/// <summary>
/// disable windows toolbar's control box
/// this will also disable system menu with Alt+Space hotkey
/// </summary>
public static void DisableControlBox(Window win)
//if current active window is desktop or shell, exit early
if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL))
{
var hwnd = new WindowInteropHelper(win).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
return false;
}
/// <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)
StringBuilder sb = new StringBuilder(256);
GetClassName(hWnd, sb, sb.Capacity);
string windowClass = sb.ToString();
//for Win+Tab (Flip3D)
if (windowClass == WINDOW_CLASS_WINTAB)
{
Matrix matrix;
var source = PresentationSource.FromVisual(visual);
if (source != null)
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 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)]
public struct RECT
/// <summary>
/// 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;
public int Top;
public int Right;
public int Bottom;
matrix = source.CompositionTarget.TransformFromDevice;
}
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:x="http://schemas.microsoft.com/winfx/2006/xaml"
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"
Height="24"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Popup
x:Name="popup"
AllowDrop="True"
AllowsTransparency="True"
IsOpen="{Binding IsKeyboardFocused, ElementName=tbHotkey, Mode=OneWay}"
Placement="Top"
PlacementTarget="{Binding ElementName=tbHotkey}"
PopupAnimation="Fade"
StaysOpen="True"
VerticalOffset="-5">
<Border
Width="140"
Height="30"
Background="{DynamicResource Color01B}"
BorderBrush="{DynamicResource Color21B}"
BorderThickness="1"
CornerRadius="4">
<TextBlock
x:Name="tbMsg"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}"
Text="{DynamicResource flowlauncherPressHotkey}"
Visibility="Visible" />
</Border>
</Popup>
<TextBox
x:Name="tbHotkey"
Margin="0,0,18,0"
VerticalContentAlignment="Center"
input:InputMethod.IsInputMethodEnabled="False"
GotFocus="tbHotkey_GotFocus"
LostFocus="tbHotkey_LostFocus"
PreviewKeyDown="TbHotkey_OnPreviewKeyDown"
TabIndex="100" />
</Grid>
</UserControl>
<Button
Width="Auto"
FontSize="13"
FontWeight="Bold"
Foreground="{DynamicResource Color01B}"
Click="GetNewHotkey">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border
x:Name="ButtonBorder"
Padding="5,0,5,0"
Background="{DynamicResource ButtonBackgroundColor}"
BorderBrush="{DynamicResource ButtonInsideBorder}"
BorderThickness="1"
CornerRadius="5">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsPressed" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ButtonMousePressed}" />
<Setter TargetName="ButtonBorder" Property="BorderBrush"
Value="{DynamicResource ButtonMousePressedInsideBorder}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ButtonMouseOver}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsPressed" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ButtonMousePressed}" />
<Setter TargetName="ButtonBorder" Property="BorderBrush"
Value="{DynamicResource CustomContextHover}" />
</MultiTrigger>
</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.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using System.Threading;
namespace Flow.Launcher
{
public partial class HotkeyControl : UserControl
public partial class HotkeyControl
{
public HotkeyModel CurrentHotkey { get; private set; }
public bool CurrentHotkeyAvailable { get; private set; }
public IHotkeySettings HotkeySettings {
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>
/// Designed for Preview Hotkey and KeyGesture.
/// </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 HotkeyControl()
public bool ValidateKeyGesture
{
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();
hotkeyUpdateSource?.Dispose();
hotkeyUpdateSource = new();
var token = hotkeyUpdateSource.Token;
e.Handled = true;
get { return (string)GetValue(DefaultHotkeyProperty); }
set { SetValue(DefaultHotkeyProperty, value); }
}
//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);
if (hotkeyModel.Equals(CurrentHotkey))
private static void OnHotkeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not HotkeyControl hotkeyControl)
{
return;
}
_ = Dispatcher.InvokeAsync(async () =>
{
await Task.Delay(500, token);
if (!token.IsCancellationRequested)
await SetHotkeyAsync(hotkeyModel);
});
hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey));
hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey);
}
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)
{
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
CurrentHotkeyAvailable = hotkeyAvailable;
SetMessage(hotkeyAvailable);
OnHotkeyChanged();
var token = hotkeyUpdateSource.Token;
await Task.Delay(500, token);
if (token.IsCancellationRequested)
return;
if (CurrentHotkeyAvailable)
if (!hotkeyAvailable)
{
CurrentHotkey = keyModel;
// To trigger LostFocus
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
Keyboard.ClearFocus();
return;
}
Hotkey = keyModel.ToString();
SetKeysToDisplay(CurrentHotkey);
ChangeHotkey?.Execute(keyModel);
}
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);
public new bool IsFocused => tbHotkey.IsFocused;
private void tbHotkey_LostFocus(object sender, RoutedEventArgs e)
private void SetKeysToDisplay(HotkeyModel? hotkey)
{
tbHotkey.Text = CurrentHotkey?.ToString() ?? "";
tbHotkey.Select(tbHotkey.Text.Length, 0);
}
KeysToDisplay.Clear();
private void tbHotkey_GotFocus(object sender, RoutedEventArgs e)
{
ResetMessage();
}
private void ResetMessage()
{
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey");
tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B");
}
private void SetMessage(bool hotkeyAvailable)
{
if (!hotkeyAvailable)
if (hotkey == null || hotkey == default(HotkeyModel))
{
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
KeysToDisplay.Add(EmptyHotkey);
return;
}
else
foreach (var key in hotkey.Value.EnumerateDisplayKeys()!)
{
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
KeysToDisplay.Add(key);
}
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="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="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="AnimationTip">Use Animation in UI</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="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="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="AnimationTip">Použít animaci v UI</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="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="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="AnimationTip">Use Animation in UI</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="SoundEffect">Soundeffekt</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="AnimationTip">Animationen in der Oberfläche verwenden</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="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="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="AnimationTip">Use Animation in UI</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String>
@ -170,14 +172,35 @@
<!-- Setting Hotkey -->
<system:String x:Key="hotkey">Hotkey</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="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="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="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="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="customQueryShortcut">Custom Query 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="edit">Edit</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="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>
@ -286,6 +310,11 @@
<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="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 -->
<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="emptyShortcut">Shortcut and/or its expansion is empty.</system:String>
<!-- Hotkey Control -->
<system:String x:Key="hotkeyUnavailable">Hotkey Unavailable</system:String>
<!-- Common Action -->
<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 -->
<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="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="RecommendWeatherDesc">Weather in Google Result</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="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="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="AnimationTip">Usar Animación en la Interfaz</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="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="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="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>
@ -73,13 +73,13 @@
<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="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="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="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="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 -->
<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="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="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="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="AnimationSpeedTip">Velocidad de animación de la interfaz de usuario</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="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="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="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>
@ -194,7 +196,7 @@
<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="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="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>
@ -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>
<!-- 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 -->
<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="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="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="AnimationTip">Utiliser l'animation dans l'interface</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="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="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="AnimationTip">Usa l'animazione nell'interfaccia utente</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="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="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="AnimationTip">Use Animation in UI</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="SoundEffect">소리 효과</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="AnimationTip">일부 UI에 애니메이션을 사용합니다.</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="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="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="AnimationTip">Use Animation in UI</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="SoundEffect">Geluidseffect</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="AnimationTip">Animatie gebruiken in UI</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="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="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="AnimationTip">Użyj animacji w interfejsie użytkownika</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="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="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="AnimationTip">Utilizar Animação na Interface</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="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="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="AnimationTip">Utilizar animações na aplicaçã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="SoundEffect">Звуковой эффект</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="AnimationTip">Использование анимации в меню</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="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="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="AnimationTip">Animovať používateľské rozhranie</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="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="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="AnimationTip">Use Animation in UI</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="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="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="AnimationTip">Arayüzde Animasyon Kullan</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="SoundEffect">Звуковий ефект</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="AnimationTip">Використовувати анімацію в інтерфейсі</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="SoundEffect">音效</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="AnimationTip">启用动画</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="SoundEffect">音效</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="AnimationTip">使用介面動畫</system:String>
<system:String x:Key="AnimationSpeed">Animation Speed</system:String>

View file

@ -46,47 +46,14 @@
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding EscCommand}" />
<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
Key="Home"
Command="{Binding SelectFirstResultCommand}"
Modifiers="Alt" />
<KeyBinding
Key="O"
Command="{Binding LoadContextMenuCommand}"
Modifiers="Ctrl" />
Key="End"
Command="{Binding SelectLastResultCommand}"
Modifiers="Alt" />
<KeyBinding
Key="R"
Command="{Binding ReQueryCommand}"
@ -111,10 +78,6 @@
Key="OemMinus"
Command="{Binding DecreaseMaxResultCommand}"
Modifiers="Control" />
<KeyBinding
Key="H"
Command="{Binding LoadHistoryCommand}"
Modifiers="Ctrl" />
<KeyBinding
Key="Enter"
Command="{Binding OpenResultCommand}"
@ -188,12 +151,52 @@
Modifiers="Ctrl" />
<KeyBinding
Key="C"
Modifiers="Ctrl+Shift"
Command="{Binding CopyAlternativeCommand}" />
Command="{Binding CopyAlternativeCommand}"
Modifiers="Ctrl+Shift" />
<KeyBinding
Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding TogglePreviewCommand}"
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>
<Grid>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
@ -216,6 +219,7 @@
<TextBox
x:Name="QueryTextBox"
AllowDrop="True"
AutomationProperties.Name="{Binding Results.SelectedItem.Result.Title}"
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
PreviewDragOver="OnPreviewDragOver"

View file

@ -26,6 +26,9 @@ using Key = System.Windows.Input.Key;
using System.Media;
using static Flow.Launcher.ViewModel.SettingWindowViewModel;
using DataObject = System.Windows.DataObject;
using System.Windows.Media;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace Flow.Launcher
{
@ -33,14 +36,17 @@ namespace Flow.Launcher
{
#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 bool isProgressBarStoryboardPaused;
private Settings _settings;
private NotifyIcon _notifyIcon;
private ContextMenu contextMenu;
private ContextMenu contextMenu = new ContextMenu();
private MainViewModel _viewModel;
private bool _animating;
SoundPlayer animationSound = new SoundPlayer(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav");
MediaPlayer animationSound = new MediaPlayer();
#endregion
@ -52,6 +58,9 @@ namespace Flow.Launcher
InitializeComponent();
InitializePosition();
animationSound.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav"));
DataObject.AddPastingHandler(QueryTextBox, OnPaste);
}
@ -128,6 +137,8 @@ namespace Flow.Launcher
{
if (_settings.UseSound)
{
animationSound.Position = TimeSpan.Zero;
animationSound.Volume = _settings.SoundVolume / 100.0;
animationSound.Play();
}
UpdatePosition();
@ -264,12 +275,11 @@ namespace Flow.Launcher
{
_notifyIcon = new NotifyIcon
{
Text = Infrastructure.Constant.FlowLauncher,
Text = Infrastructure.Constant.FlowLauncherFullName,
Icon = Properties.Resources.app,
Visible = !_settings.HideNotifyIcon
};
contextMenu = new ContextMenu();
var openIcon = new FontIcon
{
@ -277,7 +287,8 @@ namespace Flow.Launcher
};
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
{
@ -285,7 +296,8 @@ namespace Flow.Launcher
};
var gamemode = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("GameMode"), Icon = gamemodeIcon
Header = InternationalizationManager.Instance.GetTranslation("GameMode"),
Icon = gamemodeIcon
};
var positionresetIcon = new FontIcon
{
@ -293,7 +305,8 @@ namespace Flow.Launcher
};
var positionreset = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("PositionReset"), Icon = positionresetIcon
Header = InternationalizationManager.Instance.GetTranslation("PositionReset"),
Icon = positionresetIcon
};
var settingsIcon = new FontIcon
{
@ -301,7 +314,8 @@ namespace Flow.Launcher
};
var settings = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"), Icon = settingsIcon
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"),
Icon = settingsIcon
};
var exitIcon = new FontIcon
{
@ -309,7 +323,8 @@ namespace Flow.Launcher
};
var exit = new MenuItem
{
Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"), Icon = exitIcon
Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"),
Icon = exitIcon
};
open.Click += (o, e) => _viewModel.ToggleFlowLauncher();
@ -327,7 +342,6 @@ namespace Flow.Launcher
contextMenu.Items.Add(settings);
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) =>
{
switch (e.Button)
@ -335,9 +349,15 @@ namespace Flow.Launcher
case MouseButtons.Left:
_viewModel.ToggleFlowLauncher();
break;
case MouseButtons.Right:
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;
}
};

View file

@ -311,6 +311,8 @@ namespace Flow.Launcher
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 ReQuery(bool reselect = true) => _mainVM.ReQuery(reselect);
#endregion
#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>
<!-- 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>

View file

@ -104,6 +104,16 @@
<Color x:Key="NumberBoxColor26">#ffffff</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="ButtonInsideBorder" Color="#3f3f3f" />
@ -772,7 +782,7 @@
<m:StaticResource x:Key="ContentDialogForeground" ResourceKey="SystemControlPageTextBaseHighBrush" />
<m:StaticResource x:Key="ContentDialogBackground" ResourceKey="SystemControlPageBackgroundAltHighBrush" />
<m:StaticResource x:Key="ContentDialogBorderBrush" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<m:StaticResource x:Key="ContentDialogLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
<m:StaticResource x:Key="ContentDialogLightDismissOverlayBackground" ResourceKey="ContentDialogOverlayBG" />
<!-- Resources for DataGrid -->
<sys:Double x:Key="ListAccentLowOpacity">0.6</sys:Double>

View file

@ -95,6 +95,17 @@
<Color x:Key="NumberBoxColor26">#1b1b1b</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="ButtonInsideBorder" Color="#d3d3d3" />

View file

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

View file

@ -5,16 +5,14 @@ using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage2
{
private Settings Settings { get; set; }
private Brush tbMsgForegroundColorOriginal;
private string tbMsgTextOriginal;
public Settings Settings { get; set; }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
@ -22,31 +20,14 @@ namespace Flow.Launcher.Resources.Pages
Settings = settings;
else
throw new ArgumentException("Unexpected Parameter setting.");
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;
HotkeyControl.tbMsg.Foreground = tbMsgForegroundColorOriginal;
[RelayCommand]
private static void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
}
}
}

View file

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

Binary file not shown.

View file

@ -2,6 +2,7 @@
x:Class="Flow.Launcher.SettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:core="clr-namespace:Flow.Launcher.Core.Resource;assembly=Flow.Launcher.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -67,6 +68,20 @@
</Setter.Value>
</Setter>
</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">
<Setter Property="Focusable" Value="False" />
<Setter Property="Margin" Value="0" />
@ -137,6 +152,7 @@
<Setter Property="Grid.Column" Value="2" />
<Setter Property="FocusVisualMargin" Value="5" />
<Setter Property="Width" Value="Auto" />
<Setter Property="FocusVisualStyle" Value="{DynamicResource SwitchFocusVisualStyleKey}" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="OffContent" Value="{DynamicResource disable}" />
@ -447,7 +463,10 @@
IsItemsHost="true"
LastChildFill="False" />
<Border Grid.Column="1">
<ContentPresenter Grid.Column="1" ContentSource="SelectedContent" />
<ContentPresenter
x:Name="PART_SelectedContentHost"
Grid.Column="1"
ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
@ -693,7 +712,7 @@
</ItemsControl>
</Border>
<Border
<Border
Margin="0,30,0,0"
Padding="0"
Style="{DynamicResource SettingGroupBox}">
@ -720,7 +739,7 @@
<ComboBox
x:Name="SelectScreen"
MinWidth="160"
Margin="0,0,18,0"
Margin="0,0,18,0"
VerticalAlignment="Center"
FontSize="14"
ItemsSource="{Binding ScreenNumbers}"
@ -738,14 +757,12 @@
</ComboBox>
</StackPanel>
<TextBlock Style="{StaticResource Glyph}">
&#xe7f4;
&#xe7f4;
</TextBlock>
</ItemsControl>
</Border>
<Separator
Width="Auto"
BorderThickness="1">
<Separator Width="Auto" BorderThickness="1">
<Separator.Style>
<Style BasedOn="{StaticResource SettingSeparatorStyle}" TargetType="Separator">
<Setter Property="Visibility" Value="Visible" />
@ -758,9 +775,7 @@
</Separator.Style>
</Separator>
<Border
Margin="0"
BorderThickness="0">
<Border Margin="0" BorderThickness="0">
<Border.Style>
<Style BasedOn="{StaticResource SettingGroupBox}" TargetType="Border">
<Setter Property="Visibility" Value="Visible" />
@ -786,8 +801,7 @@
FontSize="14"
ItemsSource="{Binding SearchWindowAligns}"
SelectedValue="{Binding Settings.SearchWindowAlign}"
SelectedValuePath="Value">
</ComboBox>
SelectedValuePath="Value" />
<StackPanel Margin="0,0,18,0" Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
@ -817,7 +831,7 @@
</StackPanel>
</StackPanel>
<TextBlock Style="{StaticResource Glyph}">
&#xe7f4;
&#xe7f4;
</TextBlock>
</ItemsControl>
</Border>
@ -2446,8 +2460,7 @@
FontSize="14"
ItemsSource="{Binding AnimationSpeeds}"
SelectedValue="{Binding Settings.AnimationSpeed}"
SelectedValuePath="Value">
</ComboBox>
SelectedValuePath="Value" />
<StackPanel Margin="0,0,18,0" Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
@ -2474,27 +2487,81 @@
</StackPanel>
</Border>
<Border
Margin="0"
BorderThickness="0"
Margin="0,12,0,12"
Padding="0"
CornerRadius="5"
Style="{DynamicResource SettingGroupBox}">
<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
<StackPanel Orientation="Vertical">
<Border
Margin="0"
BorderThickness="0"
Style="{DynamicResource SettingGroupBox}">
<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.Column="2"
IsOn="{Binding UseSound, Mode=TwoWay}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}"
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;
</TextBlock>
</ItemsControl>
</TextBlock>
</ItemsControl>
</Border>
</StackPanel>
</Border>
<Border Margin="0,12,0,12" Style="{DynamicResource SettingGroupBox}">
@ -2581,63 +2648,39 @@
<StackPanel Margin="5,18,18,10">
<TextBlock
Grid.Row="0"
Margin="0,5,0,2"
Margin="0,5,0,6"
FontSize="30"
Style="{StaticResource PageTitle}"
Text="{DynamicResource hotkeys}"
TextAlignment="left" />
<StackPanel Grid.Row="1">
<Border Margin="0,8,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource flowlauncherHotkey}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource flowlauncherHotkeyToolTip}" />
</StackPanel>
<flowlauncher:HotkeyControl
x:Name="HotkeyControl"
Grid.Row="0"
Grid.Column="2"
Width="300"
Height="35"
Margin="0,0,0,0"
HorizontalAlignment="Right"
HorizontalContentAlignment="Right"
GotFocus="OnHotkeyControlFocused"
Loaded="OnHotkeyControlLoaded"
LostFocus="OnHotkeyControlFocusLost" />
<TextBlock Style="{StaticResource Glyph}">
&#xeda7;
</TextBlock>
</ItemsControl>
</Border>
<cc:Card
Title="{DynamicResource flowlauncherHotkey}"
Icon="&#xeda7;"
Sub="{DynamicResource flowlauncherHotkeyToolTip}">
<flowlauncher:HotkeyControl
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
Hotkey="{Binding Settings.Hotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
</cc:Card>
</StackPanel>
<StackPanel Grid.Row="2">
<Border Margin="0,8,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource previewHotkey}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource previewHotkeyToolTip}" />
</StackPanel>
<flowlauncher:HotkeyControl
x:Name="PreviewHotkeyControl"
Grid.Row="0"
Grid.Column="2"
Width="300"
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>
<cc:Card
Title="{DynamicResource previewHotkey}"
Icon="&#xe8a1;"
Sub="{DynamicResource previewHotkeyToolTip}">
<flowlauncher:HotkeyControl
DefaultHotkey="F1"
Hotkey="{Binding Settings.PreviewHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False"
WindowTitle="{DynamicResource previewHotkey}" />
</cc:Card>
</StackPanel>
<Border
@ -2697,163 +2740,374 @@
</StackPanel>
</Border>
<TextBlock
Grid.Row="4"
Margin="0,10,12,10"
Padding="0,12,0,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Color05B}"
Text="{DynamicResource customQueryHotkey}" />
<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 Grid.Row="2">
<cc:ExCard
Title="{DynamicResource hotkeyPresets}"
Margin="0,14,0,0"
Icon="&#xf0e2;"
Sub="{DynamicResource hotkeyPresetsToolTip}">
<StackPanel>
<cc:Card
Title="{DynamicResource OpenContainFolderHotkey}"
Icon="&#xe8b7;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+Enter" />
</cc:Card>
<cc:Card
Title="{DynamicResource RunAsAdminHotkey}"
Icon="&#xe7ef;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+Shift+Enter" />
</cc:Card>
<cc:Card
Title="{DynamicResource ToggleHistoryHotkey}"
Icon="&#xf738;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+H" />
</cc:Card>
<cc:Card
Title="{DynamicResource CopyFilePathHotkey}"
Icon="&#xe8c8;"
Type="Inside">
<cc:HotkeyDisplay Keys="Ctrl+Shift+C" />
</cc:Card>
<cc:Card
Title="{DynamicResource OpenContextMenuHotkey}"
Icon="&#xede3;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey="Ctrl+I"
Hotkey="{Binding Settings.OpenContextMenuHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
Title="{DynamicResource OpenContextMenuHotkey}"
Icon="&#xede3;"
Type="Inside">
<cc:HotkeyDisplay Keys="Shift+Enter" />
</cc:Card>
<cc:Card
Title="{DynamicResource SettingWindowHotkey}"
Icon="&#xe713;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey="Ctrl+I"
Hotkey="{Binding Settings.SettingWindowHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</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>
<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>
</Border>
</ScrollViewer>

View file

@ -17,6 +17,7 @@ using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Navigation;
using NHotkey;
using Button = System.Windows.Controls.Button;
using Control = System.Windows.Controls.Control;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
@ -110,41 +111,9 @@ namespace Flow.Launcher
#region Hotkey
private void OnHotkeyControlLoaded(object sender, RoutedEventArgs e)
private void OnToggleHotkey(object sender, HotkeyEventArgs e)
{
_ = HotkeyControl.SetHotkeyAsync(viewModel.Settings.Hotkey, false);
}
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();
}
HotKeyMapper.OnToggleHotkey(sender, e);
}
private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e)
@ -391,7 +360,7 @@ namespace Flow.Launcher
{
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):
OnPropertyChanged(nameof(PreviewHotkey));
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]
private void ReQuery()
public void ReQuery()
{
if (SelectedIsFromQueryResults())
{
@ -218,6 +248,14 @@ namespace Flow.Launcher.ViewModel
}
}
public void ReQuery(bool reselect)
{
if (SelectedIsFromQueryResults())
{
QueryResults(isReQuery: true, reSelect: reselect);
}
}
[RelayCommand]
private void LoadContextMenu()
{
@ -334,6 +372,13 @@ namespace Flow.Launcher.ViewModel
SelectedResults.SelectFirstResult();
}
[RelayCommand]
private void SelectLastResult()
{
SelectedResults.SelectLastResult();
}
[RelayCommand]
private void SelectPrevPage()
{
@ -619,25 +664,33 @@ namespace Flow.Launcher.ViewModel
public string OpenResultCommandModifiers => Settings.OpenResultModifiers;
public string PreviewHotkey
public string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotkey)
{
get
try
{
// TODO try to patch issue #1755
// Added in v1.14.0, remove after v1.16.0.
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;
var converter = new KeyGestureConverter();
var key = (KeyGesture)converter.ConvertFromString(hotkey);
}
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;
@ -680,20 +733,7 @@ namespace Flow.Launcher.ViewModel
List<Result> results;
if (selected == lastContextMenuResult)
{
// Use copy to keep the original results unchanged
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,
});
results = lastContextMenuResults;
}
else
{
@ -707,7 +747,7 @@ namespace Flow.Launcher.ViewModel
if (!string.IsNullOrEmpty(query))
{
var filtered = results.Where
var filtered = results.Select(x => x.Clone()).Where
(
r =>
{
@ -775,7 +815,7 @@ namespace Flow.Launcher.ViewModel
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();
@ -850,7 +890,7 @@ namespace Flow.Launcher.ViewModel
var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
{
false => QueryTask(plugin),
false => QueryTask(plugin, reSelect),
true => Task.CompletedTask
}).ToArray();
@ -878,7 +918,7 @@ namespace Flow.Launcher.ViewModel
}
// 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
// Task.Yield will force it to run in ThreadPool
@ -892,7 +932,7 @@ namespace Flow.Launcher.ViewModel
results ??= _emptyResult;
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query,
currentCancellationToken)))
currentCancellationToken, reSelect)))
{
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
}
@ -1133,7 +1173,7 @@ namespace Flow.Launcher.ViewModel
/// <summary>
/// To avoid deadlock, this method should not called from main thread
/// </summary>
public void UpdateResultView(IEnumerable<ResultsForUpdate> resultsForUpdates)
public void UpdateResultView(ICollection<ResultsForUpdate> resultsForUpdates)
{
if (!resultsForUpdates.Any())
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

View file

@ -4,23 +4,13 @@ using System.Threading;
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 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;
}
public string ID { get; } = Metadata.ID;
}
}

View file

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

View file

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

View file

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

View file

@ -3,23 +3,22 @@ using System;
using System.Collections.Generic;
using System.IO;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{
public override List<Bookmark> GetBookmarks()
{
return LoadChromeBookmarks();
}
namespace Flow.Launcher.Plugin.BrowserBookmark;
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;
}
public class ChromeBookmarkLoader : ChromiumBookmarkLoader
{
public override List<Bookmark> GetBookmarks()
{
return LoadChromeBookmarks();
}
}
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 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>();
if (!Directory.Exists(browserDataPath)) return bookmarks;
var paths = Directory.GetDirectories(browserDataPath);
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
foreach (var profile in paths)
{
var bookmarkPath = Path.Combine(profile, "Bookmarks");
if (!File.Exists(bookmarkPath))
continue;
Main.RegisterBookmarkFile(bookmarkPath);
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)})");
bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source));
}
return bookmarks;
}
protected List<Bookmark> LoadBookmarksFromFile(string path, string source)
{
var bookmarks = new List<Bookmark>();
if (!File.Exists(path))
return bookmarks;
}
protected List<Bookmark> LoadBookmarksFromFile(string path, string source)
{
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);
using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path));
if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement))
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.
// If various exceptions start to build up here consider splitting this Loader into multiple separate ones.
if (folder.Name == "custom_root")
EnumerateRoot(folder.Value, bookmarks, source);
else
EnumerateFolderBookmark(folder.Value, bookmarks, source);
// 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 (folder.Name == "custom_root")
EnumerateRoot(folder.Value, bookmarks, source);
else
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;
}
}
}
private void EnumerateFolderBookmark(JsonElement folderElement, ICollection<Bookmark> bookmarks,
string source)
{
if (!folderElement.TryGetProperty("children", out var childrenElement))
return;
foreach (var subElement in childrenElement.EnumerateArray())
else
{
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
{
Log.Error(
$"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}");
}
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.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())
return match;
var match = StringMatcher.FuzzySearch(queryString, bookmark.Name);
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>();
if (setting.LoadChromeBookmark)
{
// 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();
// 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();
}
}

View file

@ -1,19 +1,18 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models;
using System.Collections.Generic;
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; }
namespace Flow.Launcher.Plugin.BrowserBookmark;
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 Flow.Launcher.Plugin.BrowserBookmark.Models;
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; }
namespace Flow.Launcher.Plugin.BrowserBookmark;
public override List<Bookmark> GetBookmarks()
{
return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite"));
}
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()
{
return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite"));
}
}

View file

@ -3,21 +3,20 @@ using System;
using System.Collections.Generic;
using System.IO;
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"));
namespace Flow.Launcher.Plugin.BrowserBookmark;
return bookmarks;
}
public override List<Bookmark> GetBookmarks() => LoadEdgeBookmarks();
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 override List<Bookmark> GetBookmarks() => LoadEdgeBookmarks();
}

View file

@ -5,140 +5,133 @@ using System.Collections.Generic;
using System.IO;
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
FROM moz_places
INNER JOIN moz_bookmarks ON (
private const string QueryAllBookmarks = """
SELECT moz_places.url, moz_bookmarks.title
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
)
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)
{
// Return empty list if the places.sqlite file cannot be found
if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath))
return new List<Bookmark>();
protected static List<Bookmark> GetBookmarksFromPath(string placesPath)
{
// Return empty list if the places.sqlite file cannot be found
if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath))
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
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();
// return results in List<Bookmark> format
bookmarkList = reader.Select(
x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(),
x["url"].ToString())
).ToList();
// return results in List<Bookmark> format
return reader
.Select(
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);
}
public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
/// <summary>
/// Path to places.sqlite
/// </summary>
private string PlacesPath
{
/// <summary>
/// Searches the places.sqlite db and returns all bookmarks
/// </summary>
public override List<Bookmark> GetBookmarks()
get
{
return GetBookmarksFromPath(PlacesPath);
}
/// <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");
var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox");
var profileIni = Path.Combine(profileFolderPath, @"profiles.ini");
if (!File.Exists(profileIni))
return string.Empty;
if (!File.Exists(profileIni))
return string.Empty;
// get firefox default profile directory from profiles.ini
string ini;
using (var sReader = new StreamReader(profileIni))
{
ini = sReader.ReadToEnd();
}
// get firefox default profile directory from profiles.ini
using var sReader = new StreamReader(profileIni);
var ini = sReader.ReadToEnd();
/*
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
/*
Current profiles.ini structure example as of Firefox version 69.0.1
[Profile2]
Name=newblahprofile
IsRelative=0
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.
[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
[Profile1]
Name=default
IsRelative=1
Path=Profiles/cydum7q4.default
Default=1
[Profile2]
Name=newblahprofile
IsRelative=0
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.
[Profile0]
Name=default-release
IsRelative=1
Path=Profiles/7789f565.default-release
[Profile1]
Name=default
IsRelative=1
Path=Profiles/cydum7q4.default
Default=1
[General]
StartWithLastProfile=1
Version=2
*/
[Profile0]
Name=default-release
IsRelative=1
Path=Profiles/7789f565.default-release
var lines = ini.Split(new string[]
{
"\r\n"
}, StringSplitOptions.None).ToList();
[General]
StartWithLastProfile=1
Version=2
*/
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))
return string.Empty;
if (string.IsNullOrEmpty(defaultProfileFolderNameRaw))
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
var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1];
// Seen in the example above, the IsRelative attribute is always above the Path attribute
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
? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
}
}
}
public static class Extensions
{
public static IEnumerable<T> Select<T>(this SqliteDataReader reader, Func<SqliteDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
? defaultProfileFolderName + @"\places.sqlite"
: Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite";
}
}
}
public static class Extensions
{
public static IEnumerable<T> Select<T>(this SqliteDataReader reader, Func<SqliteDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
}
}

View file

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

View file

@ -1,10 +1,9 @@
using Flow.Launcher.Plugin.BrowserBookmark.Models;
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.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin.BrowserBookmark.Commands;
@ -12,233 +11,234 @@ using System.Threading.Channels;
using System.Threading.Tasks;
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;
public void Init(PluginInitContext context)
private static void LoadBookmarksIfEnabled()
{
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();
}
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)
{
// Don't load or monitor files if disabled
return;
}
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 = _ =>
// Since we mixed chrome and firefox bookmarks, we should order them again
return _cachedBookmarks
.Select(
c => new Result
{
context.API.OpenUrl(c.Url);
return true;
},
ContextData = new BookmarkAttributes
{
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
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = BookmarkLoader.MatchProgram(c, param).Score,
Action = _ =>
{
context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url);
_context.API.OpenUrl(c.Url);
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";
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();
_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);
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;
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 record Bookmark(string Name, string Url, string Source = "")
public override int GetHashCode()
{
public override int GetHashCode()
{
var hashName = Name?.GetHashCode() ?? 0;
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();
var hashName = Name?.GetHashCode() ?? 0;
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();
}

View file

@ -1,45 +1,44 @@
namespace Flow.Launcher.Plugin.BrowserBookmark.Models
{
public class CustomBrowser : BaseModel
{
private string _name;
private string _dataDirectoryPath;
private BrowserType browserType = BrowserType.Chromium;
namespace Flow.Launcher.Plugin.BrowserBookmark.Models;
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;
set
{
_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));
}
_name = value;
OnPropertyChanged();
}
}
public enum BrowserType
public string DataDirectoryPath
{
Chromium,
Firefox,
get => _dataDirectoryPath;
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;
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 LoadFirefoxBookmark { get; set; } = true;
public bool LoadEdgeBookmark { get; set; } = true;
public bool LoadChromeBookmark { get; set; } = true;
public bool LoadFirefoxBookmark { 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