mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Revise Hotkey Structure
- Put all logic to HotkeyControlViewModel.cs - Remove logic about GetFocus/LostFocus - RegisterCallBack (Delegate)
This commit is contained in:
parent
6e34218f8e
commit
453f767188
9 changed files with 354 additions and 266 deletions
|
|
@ -2,6 +2,7 @@
|
|||
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"
|
||||
|
|
@ -11,7 +12,8 @@
|
|||
MouseDown="window_MouseDown"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
WindowStartupLocation="CenterScreen"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
|
||||
</WindowChrome.WindowChrome>
|
||||
|
|
@ -104,7 +106,7 @@
|
|||
Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<flowlauncher:HotkeyControl
|
||||
x:Name="ctlHotkey"
|
||||
DataContext="{Binding HotkeyControlViewModel}"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Height="36"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -32,7 +33,7 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (!update)
|
||||
{
|
||||
if (!ctlHotkey.CurrentHotkeyAvailable)
|
||||
if (!HotkeyControlViewModel.CurrentHotkeyAvailable)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("hotkeyIsNotUnavailable"));
|
||||
return;
|
||||
|
|
@ -45,8 +46,7 @@ namespace Flow.Launcher
|
|||
|
||||
var pluginHotkey = new CustomPluginHotkey
|
||||
{
|
||||
Hotkey = ctlHotkey.CurrentHotkey.ToString(),
|
||||
ActionKeyword = tbAction.Text
|
||||
Hotkey = HotkeyControlViewModel.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
|
||||
};
|
||||
_settings.CustomPluginHotkeys.Add(pluginHotkey);
|
||||
|
||||
|
|
@ -54,14 +54,16 @@ namespace Flow.Launcher
|
|||
}
|
||||
else
|
||||
{
|
||||
if (updateCustomHotkey.Hotkey != ctlHotkey.CurrentHotkey.ToString() && !ctlHotkey.CurrentHotkeyAvailable)
|
||||
if (updateCustomHotkey.Hotkey != HotkeyControlViewModel.CurrentHotkey.ToString() &&
|
||||
!HotkeyControlViewModel.CurrentHotkeyAvailable)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("hotkeyIsNotUnavailable"));
|
||||
return;
|
||||
}
|
||||
|
||||
var oldHotkey = updateCustomHotkey.Hotkey;
|
||||
updateCustomHotkey.ActionKeyword = tbAction.Text;
|
||||
updateCustomHotkey.Hotkey = ctlHotkey.CurrentHotkey.ToString();
|
||||
updateCustomHotkey.Hotkey = HotkeyControlViewModel.CurrentHotkey.ToString();
|
||||
//remove origin hotkey
|
||||
HotKeyMapper.RemoveHotkey(oldHotkey);
|
||||
HotKeyMapper.SetCustomQueryHotkey(updateCustomHotkey);
|
||||
|
|
@ -70,9 +72,12 @@ namespace Flow.Launcher
|
|||
Close();
|
||||
}
|
||||
|
||||
public HotkeyControlViewModel HotkeyControlViewModel { get; set; } = new HotkeyControlViewModel();
|
||||
|
||||
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 +86,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
|
||||
tbAction.Text = updateCustomHotkey.ActionKeyword;
|
||||
_ = ctlHotkey.SetHotkeyAsync(updateCustomHotkey.Hotkey, false);
|
||||
_ = HotkeyControlViewModel.SetHotkeyAsync(updateCustomHotkey.Hotkey, false);
|
||||
update = true;
|
||||
lblAdd.Text = InternationalizationManager.Instance.GetTranslation("update");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
|
||||
d:DesignWidth="300"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance vm:HotkeyControlViewModel}">
|
||||
<Grid>
|
||||
<Popup
|
||||
x:Name="popup"
|
||||
|
|
@ -43,9 +45,10 @@
|
|||
FontSize="13"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource Color01B}"
|
||||
Click="OnStartRecordingClicked"
|
||||
Command="{Binding StartRecordingCommand}"
|
||||
PreviewKeyDown="OnPreviewKeyDown"
|
||||
KeyboardNavigation.TabNavigation="Continue">
|
||||
KeyboardNavigation.TabNavigation="Continue"
|
||||
IsChecked="{Binding Recording}">
|
||||
<ToggleButton.Template>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid>
|
||||
|
|
@ -62,9 +65,9 @@
|
|||
BorderThickness="1"
|
||||
CornerRadius="5">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="Reset" Click="OnResetToDefaultClicked" />
|
||||
<Button Content="Delete" Click="OnDeleteClicked" />
|
||||
<Button Content="Stop Rec" Click="OnStopRecordingClicked" />
|
||||
<Button Content="Reset" Command="{Binding ResetToDefaultCommand}" />
|
||||
<Button Content="Delete" Command="{Binding DeleteCommand}" />
|
||||
<Button Content="Stop Rec" Command="{Binding StopRecordingCommand}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
|
@ -100,7 +103,8 @@
|
|||
</ControlTemplate>
|
||||
</ToggleButton.Template>
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl ItemsSource="{Binding Path=KeysToDisplay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Path=KeysToDisplay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
|
|
@ -122,4 +126,4 @@
|
|||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
|
@ -10,141 +10,42 @@ using Flow.Launcher.Core.Resource;
|
|||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using NHotkey;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class HotkeyControl : UserControl, IDisposable, INotifyPropertyChanged
|
||||
public partial class HotkeyControl : UserControl, INotifyPropertyChanged
|
||||
{
|
||||
public static readonly DependencyProperty HotkeyProperty = DependencyProperty.Register(
|
||||
nameof(Hotkey),
|
||||
typeof(string),
|
||||
typeof(HotkeyControl),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
|
||||
);
|
||||
public HotkeyModel CurrentHotkey { get; private set; }
|
||||
public bool CurrentHotkeyAvailable { get; private set; }
|
||||
|
||||
public string Hotkey
|
||||
{
|
||||
get => (string)GetValue(HotkeyProperty);
|
||||
set
|
||||
{
|
||||
SetValue(HotkeyProperty, value);
|
||||
OnPropertyChanged(nameof(KeysToDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
public string DefaultHotkey { get; set; }
|
||||
|
||||
private const string EmptyKeyValue = "<empty>";
|
||||
private const string KeySeparator = " + ";
|
||||
|
||||
private string[] _keysToDisplay = { EmptyKeyValue };
|
||||
|
||||
public string[] KeysToDisplay
|
||||
{
|
||||
get => _keysToDisplay;
|
||||
set
|
||||
{
|
||||
_keysToDisplay = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public EventHandler<HotkeyEventArgs>? Action { get; set; }
|
||||
#nullable restore
|
||||
|
||||
public event EventHandler HotkeyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Designed for Preview Hotkey and KeyGesture.
|
||||
/// </summary>
|
||||
public bool ValidateKeyGesture { get; set; } = false;
|
||||
|
||||
protected virtual void OnHotkeyChanged() => HotkeyChanged?.Invoke(this, EventArgs.Empty);
|
||||
// /// <summary>
|
||||
// /// Designed for Preview Hotkey and KeyGesture.
|
||||
// /// </summary>
|
||||
// public static readonly DependencyProperty ValidateKeyGestureProperty = DependencyProperty.Register(
|
||||
// nameof(ValidateKeyGesture), typeof(bool), typeof(HotkeyControlViewModel),
|
||||
// new PropertyMetadata(default(bool)));
|
||||
//
|
||||
// public bool ValidateKeyGesture
|
||||
// {
|
||||
// get { return (bool)GetValue(ValidateKeyGestureProperty); }
|
||||
// set { SetValue(ValidateKeyGestureProperty, value); }
|
||||
// }
|
||||
//
|
||||
// public static readonly DependencyProperty DefaultHotkeyProperty = DependencyProperty.Register(
|
||||
// nameof(DefaultHotkey), typeof(string), typeof(HotkeyControl), new PropertyMetadata(default(string)));
|
||||
//
|
||||
// public string DefaultHotkey
|
||||
// {
|
||||
// get { return (string)GetValue(DefaultHotkeyProperty); }
|
||||
// set { SetValue(DefaultHotkeyProperty, value); }
|
||||
// }
|
||||
|
||||
public HotkeyControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += HotkeyControl_Loaded;
|
||||
}
|
||||
|
||||
/*------------------ New Logic Structure Part ------------------------*/
|
||||
|
||||
private void OnStartRecordingClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (HotkeyBtn.IsChecked == true)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
{
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
}
|
||||
/* 1. Key Recording Start */
|
||||
/* 2. Key Display area clear
|
||||
* 3. Key Display when typing*/
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStopRecordingClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HotkeyBtn.IsChecked = false;
|
||||
|
||||
if (KeysToDisplay.Length == 0 || (KeysToDisplay.Length == 1 && KeysToDisplay[0] == EmptyKeyValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_ = SetHotkeyAsync(string.Join(KeySeparator, KeysToDisplay));
|
||||
}
|
||||
|
||||
private void OnResetToDefaultClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HotkeyBtn.IsChecked = false;
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
Hotkey = DefaultHotkey;
|
||||
KeysToDisplay = Hotkey.Split(KeySeparator);
|
||||
_ = SetHotkeyAsync(Hotkey);
|
||||
}
|
||||
|
||||
private void OnDeleteClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HotkeyBtn.IsChecked = false;
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
Hotkey = "";
|
||||
KeysToDisplay = new []{ EmptyKeyValue };
|
||||
}
|
||||
|
||||
/*------------------ New Logic Structure Part------------------------*/
|
||||
|
||||
private void HotkeyControl_LostFocus(object o, RoutedEventArgs routedEventArgs)
|
||||
{
|
||||
HotKeyMapper.SetHotkey(CurrentHotkey, Action);
|
||||
}
|
||||
|
||||
private void HotkeyControl_GotFocus(object o, RoutedEventArgs routedEventArgs)
|
||||
{
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
}
|
||||
|
||||
private void HotkeyControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_ = SetHotkeyAsync(Hotkey, false);
|
||||
KeysToDisplay = Hotkey switch
|
||||
{
|
||||
null or "" => new[] { EmptyKeyValue },
|
||||
_ => Hotkey.Split(KeySeparator)
|
||||
};
|
||||
|
||||
if (Action is not null)
|
||||
{
|
||||
GotFocus += HotkeyControl_GotFocus;
|
||||
LostFocus += HotkeyControl_LostFocus;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (HotkeyBtn.IsChecked != true)
|
||||
|
|
@ -163,49 +64,9 @@ namespace Flow.Launcher
|
|||
specialKeyState.CtrlPressed,
|
||||
key);
|
||||
|
||||
if (hotkeyModel.Equals(CurrentHotkey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
KeysToDisplay = hotkeyModel.ToString().Split(KeySeparator);
|
||||
((HotkeyControlViewModel)this.DataContext).KeyDown(hotkeyModel);
|
||||
}
|
||||
|
||||
public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true)
|
||||
{
|
||||
// tbHotkey.Text = keyModel.ToString();
|
||||
// tbHotkey.Select(tbHotkey.Text.Length, 0);
|
||||
|
||||
if (triggerValidate)
|
||||
{
|
||||
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
|
||||
CurrentHotkeyAvailable = hotkeyAvailable;
|
||||
SetMessage(hotkeyAvailable);
|
||||
OnHotkeyChanged();
|
||||
|
||||
if (CurrentHotkeyAvailable)
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
Hotkey = keyModel.ToString();
|
||||
KeysToDisplay = Hotkey.Split(KeySeparator);
|
||||
// To trigger LostFocus
|
||||
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
|
||||
Keyboard.ClearFocus();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
Hotkey = keyModel.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public Task SetHotkeyAsync(string keyStr, bool triggerValidate = true)
|
||||
{
|
||||
return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate);
|
||||
}
|
||||
|
||||
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
|
||||
|
||||
// public new bool IsFocused => tbHotkey.IsFocused;
|
||||
|
||||
|
|
@ -226,28 +87,6 @@ namespace Flow.Launcher
|
|||
tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B");
|
||||
}
|
||||
|
||||
private void SetMessage(bool hotkeyAvailable)
|
||||
{
|
||||
if (!hotkeyAvailable)
|
||||
{
|
||||
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
|
||||
}
|
||||
else
|
||||
{
|
||||
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
|
||||
}
|
||||
tbMsg.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Loaded -= HotkeyControl_Loaded;
|
||||
GotFocus -= HotkeyControl_GotFocus;
|
||||
LostFocus -= HotkeyControl_LostFocus;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
Title="WelcomePage2"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||
<Page.Resources>
|
||||
<converters:BorderClipConverter x:Key="BorderClipConverter" />
|
||||
<Style x:Key="StyleImageFadeIn" TargetType="{x:Type Image}">
|
||||
|
|
@ -111,6 +112,7 @@
|
|||
Text="{DynamicResource flowlauncherHotkey}" />
|
||||
<flowlauncher:HotkeyControl
|
||||
x:Name="HotkeyControl"
|
||||
DataContext="{Binding Path=HotkeyControlViewModel}"
|
||||
Width="300"
|
||||
Height="35"
|
||||
Margin="-206,10,0,0"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Navigation;
|
||||
using Flow.Launcher.ViewModel;
|
||||
|
||||
namespace Flow.Launcher.Resources.Pages
|
||||
{
|
||||
|
|
@ -16,6 +17,11 @@ namespace Flow.Launcher.Resources.Pages
|
|||
|
||||
private string tbMsgTextOriginal;
|
||||
|
||||
public HotkeyControlViewModel HotkeyControlViewModel { get; set; }= new HotkeyControlViewModel()
|
||||
{
|
||||
ValidateKeyGesture = true
|
||||
};
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (e.ExtraData is Settings settings)
|
||||
|
|
@ -24,10 +30,11 @@ namespace Flow.Launcher.Resources.Pages
|
|||
throw new ArgumentException("Unexpected Parameter setting.");
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
tbMsgTextOriginal = HotkeyControl.tbMsg.Text;
|
||||
tbMsgForegroundColorOriginal = HotkeyControl.tbMsg.Foreground;
|
||||
|
||||
HotkeyControl.SetHotkeyAsync(Settings.Hotkey, false);
|
||||
_ = HotkeyControlViewModel.SetHotkeyAsync(Settings.Hotkey, false);
|
||||
}
|
||||
private void HotkeyControl_OnGotFocus(object sender, RoutedEventArgs args)
|
||||
{
|
||||
|
|
@ -35,10 +42,10 @@ namespace Flow.Launcher.Resources.Pages
|
|||
}
|
||||
private void HotkeyControl_OnLostFocus(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (HotkeyControl.CurrentHotkeyAvailable)
|
||||
if (HotkeyControlViewModel.CurrentHotkeyAvailable)
|
||||
{
|
||||
HotKeyMapper.SetHotkey(HotkeyControl.CurrentHotkey, HotKeyMapper.OnToggleHotkey);
|
||||
Settings.Hotkey = HotkeyControl.CurrentHotkey.ToString();
|
||||
HotKeyMapper.SetHotkey(HotkeyControlViewModel.CurrentHotkey, HotKeyMapper.OnToggleHotkey);
|
||||
Settings.Hotkey = HotkeyControlViewModel.CurrentHotkey.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2643,15 +2643,12 @@
|
|||
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource flowlauncherHotkeyToolTip}" />
|
||||
</StackPanel>
|
||||
<flowlauncher:HotkeyControl
|
||||
x:Name="HotkeyControl"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,16,0"
|
||||
HorizontalAlignment="Right"
|
||||
HorizontalContentAlignment="Right"
|
||||
DefaultHotkey="Alt + Space"
|
||||
Action="OnToggleHotkey"
|
||||
Hotkey="{Binding Settings.Hotkey}" />
|
||||
DataContext="{Binding ToggleHotkeyViewModel}"/>
|
||||
|
||||
<TextBlock Style="{StaticResource Glyph}">
|
||||

|
||||
|
|
@ -2673,10 +2670,7 @@
|
|||
Grid.Column="2"
|
||||
Margin="0,0,16,0"
|
||||
HorizontalAlignment="Right"
|
||||
HorizontalContentAlignment="Right"
|
||||
DefaultHotkey="F1"
|
||||
Hotkey="{Binding Settings.PreviewHotkey}"
|
||||
ValidateKeyGesture="True" />
|
||||
HorizontalContentAlignment="Right" />
|
||||
<TextBlock Style="{StaticResource Glyph}">
|
||||

|
||||
</TextBlock>
|
||||
|
|
|
|||
224
Flow.Launcher/ViewModel/HotkeyControlViewModel.cs
Normal file
224
Flow.Launcher/ViewModel/HotkeyControlViewModel.cs
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Plugin;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public partial class HotkeyControlViewModel : BaseModel
|
||||
{
|
||||
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
|
||||
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
|
||||
|
||||
public string? Message { get; set; }
|
||||
public bool MessageVisibility { get; set; }
|
||||
public SolidColorBrush? MessageColor { get; set; }
|
||||
|
||||
|
||||
public bool CurrentHotkeyAvailable { get; private set; }
|
||||
|
||||
public string DefaultHotkey { get; init; }
|
||||
|
||||
private void SetMessage(bool hotkeyAvailable)
|
||||
{
|
||||
Message = hotkeyAvailable
|
||||
? InternationalizationManager.Instance.GetTranslation("success")
|
||||
: InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
|
||||
MessageColor = hotkeyAvailable ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
|
||||
MessageVisibility = true;
|
||||
}
|
||||
|
||||
public HotkeyControlViewModel(string hotkey = "",
|
||||
string defaultHotkey = "",
|
||||
bool validateKeyGesture = true,
|
||||
Action<HotkeyModel>? hotkeyDelegate = null)
|
||||
{
|
||||
Hotkey = hotkey;
|
||||
DefaultHotkey = defaultHotkey;
|
||||
ValidateKeyGesture = validateKeyGesture;
|
||||
HotkeyDelegate = hotkeyDelegate;
|
||||
|
||||
if (string.IsNullOrEmpty(Hotkey))
|
||||
{
|
||||
Hotkey = DefaultHotkey;
|
||||
}
|
||||
|
||||
SetKeysToDisplay(Hotkey?.Split(KeySeparator));
|
||||
}
|
||||
|
||||
|
||||
private const string EmptyKeyValue = "<empty>";
|
||||
private const string KeySeparator = " + ";
|
||||
|
||||
public ObservableCollection<string> KeysToDisplay { get; } = new() { EmptyKeyValue };
|
||||
|
||||
|
||||
public HotkeyModel CurrentHotkey { get; private set; }
|
||||
|
||||
public string Hotkey { get; set; }
|
||||
|
||||
|
||||
public bool Recording { get; set; }
|
||||
|
||||
[RelayCommand]
|
||||
public async Task StartRecordingAsync()
|
||||
{
|
||||
if (!Recording)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
{
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
}
|
||||
/* 1. Key Recording Start */
|
||||
/* 2. Key Display area clear
|
||||
* 3. Key Display when typing*/
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task StopRecordingAsync()
|
||||
{
|
||||
Recording = false;
|
||||
|
||||
if (KeysToDisplay.Count == 0 || (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyKeyValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await SetHotkeyAsync(CurrentHotkey, true);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ResetToDefaultAsync()
|
||||
{
|
||||
Recording = false;
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
Hotkey = DefaultHotkey;
|
||||
|
||||
KeysToDisplay.Clear();
|
||||
|
||||
foreach (var key in Hotkey.Split(KeySeparator))
|
||||
{
|
||||
KeysToDisplay.Add(key);
|
||||
}
|
||||
|
||||
await SetHotkeyAsync(Hotkey);
|
||||
}
|
||||
|
||||
public bool ValidateKeyGesture { get; set; }
|
||||
public Action<HotkeyModel> HotkeyDelegate { get; set; }
|
||||
|
||||
private async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true)
|
||||
{
|
||||
// tbHotkey.Text = keyModel.ToString();
|
||||
// tbHotkey.Select(tbHotkey.Text.Length, 0);
|
||||
|
||||
if (triggerValidate)
|
||||
{
|
||||
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
|
||||
CurrentHotkeyAvailable = hotkeyAvailable;
|
||||
SetMessage(hotkeyAvailable);
|
||||
HotkeyDelegate?.Invoke(keyModel);
|
||||
|
||||
if (CurrentHotkeyAvailable)
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
Hotkey = keyModel.ToString();
|
||||
SetKeysToDisplay(Hotkey.Split(KeySeparator));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
Hotkey = keyModel.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DeleteAsync()
|
||||
{
|
||||
Recording = false;
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
HotKeyMapper.RemoveHotkey(Hotkey);
|
||||
Hotkey = "";
|
||||
SetKeysToDisplay(new List<string>());
|
||||
}
|
||||
|
||||
private void SetKeysToDisplay(HotkeyModel hotkey)
|
||||
{
|
||||
KeysToDisplay.Clear();
|
||||
if (hotkey.Alt)
|
||||
{
|
||||
KeysToDisplay.Add("Alt");
|
||||
}
|
||||
|
||||
if (hotkey.Ctrl)
|
||||
{
|
||||
KeysToDisplay.Add("Ctrl");
|
||||
}
|
||||
|
||||
if (hotkey.Shift)
|
||||
{
|
||||
KeysToDisplay.Add("Shift");
|
||||
}
|
||||
|
||||
if (hotkey.Win)
|
||||
{
|
||||
KeysToDisplay.Add("Win");
|
||||
}
|
||||
|
||||
if (hotkey.CharKey != Key.None)
|
||||
{
|
||||
KeysToDisplay.Add(hotkey.CharKey.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetKeysToDisplay(ICollection<string>? keys)
|
||||
{
|
||||
KeysToDisplay.Clear();
|
||||
|
||||
if (keys == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
KeysToDisplay.Add(key);
|
||||
}
|
||||
|
||||
if (!keys.Any())
|
||||
{
|
||||
KeysToDisplay.Add(EmptyKeyValue);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SetHotkeyAsync(string? keyStr, bool triggerValidate = true)
|
||||
{
|
||||
return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate);
|
||||
}
|
||||
|
||||
public void KeyDown(HotkeyModel hotkeyModel)
|
||||
{
|
||||
CurrentHotkey = hotkeyModel;
|
||||
SetKeysToDisplay(hotkeyModel.ToString().Split(KeySeparator));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ using Flow.Launcher.Plugin.SharedModels;
|
|||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -69,6 +70,12 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
};
|
||||
|
||||
ToggleHotkeyViewModel =
|
||||
new HotkeyControlViewModel(Settings.Hotkey, "Alt + Space", true, hotkey =>
|
||||
{
|
||||
Settings.Hotkey = hotkey.ToString();
|
||||
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
|
||||
});
|
||||
}
|
||||
|
||||
public Settings Settings { get; set; }
|
||||
|
|
@ -110,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;
|
||||
|
|
@ -185,6 +194,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
private List<LastQueryMode> _lastQueryModes = new List<LastQueryMode>();
|
||||
|
||||
public List<LastQueryMode> LastQueryModes
|
||||
{
|
||||
get
|
||||
|
|
@ -193,6 +203,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
_lastQueryModes = InitLastQueryModes();
|
||||
}
|
||||
|
||||
return _lastQueryModes;
|
||||
}
|
||||
}
|
||||
|
|
@ -200,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;
|
||||
}
|
||||
|
||||
|
|
@ -267,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()
|
||||
{
|
||||
|
|
@ -285,6 +295,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty");
|
||||
}
|
||||
|
||||
if (Settings.Proxy.Port <= 0)
|
||||
{
|
||||
return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty");
|
||||
|
|
@ -303,6 +314,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password)
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = (HttpWebResponse)request.GetResponse();
|
||||
|
|
@ -333,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();
|
||||
}
|
||||
|
||||
|
|
@ -383,8 +392,7 @@ namespace Flow.Launcher.ViewModel
|
|||
await PluginsManifest.UpdateManifestAsync();
|
||||
OnPropertyChanged(nameof(ExternalPlugins));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0)
|
||||
{
|
||||
|
|
@ -458,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -484,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -511,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -531,6 +532,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
screenNumbers.Add(i);
|
||||
}
|
||||
|
||||
return screenNumbers;
|
||||
}
|
||||
}
|
||||
|
|
@ -617,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -690,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
|
||||
|
|
@ -714,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);
|
||||
|
|
@ -889,6 +895,7 @@ namespace Flow.Launcher.ViewModel
|
|||
SelectedCustomShortcut = item;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -917,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
|
||||
|
|
@ -931,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
|
||||
{
|
||||
|
|
@ -939,10 +949,13 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeyControlViewModel ToggleHotkeyViewModel { get; set; }
|
||||
|
||||
private static DirectoryInfo GetLogDir(string version = "")
|
||||
{
|
||||
return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version));
|
||||
|
|
@ -976,10 +989,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)
|
||||
|
|
@ -989,6 +999,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
max /= scale;
|
||||
}
|
||||
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue