Skip to content

Commit

Permalink
implement KeyBinding
Browse files Browse the repository at this point in the history
  • Loading branch information
MikiraSora committed Oct 20, 2024
1 parent f808871 commit 98555a6
Show file tree
Hide file tree
Showing 16 changed files with 880 additions and 0 deletions.
143 changes: 143 additions & 0 deletions OngekiFumenEditor/Kernel/KeyBinding/DefaultKeyBindingManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using OngekiFumenEditor.Properties;
using OngekiFumenEditor.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Input;

namespace OngekiFumenEditor.Kernel.KeyBinding
{
[Export(typeof(IKeyBindingManager))]
[PartCreationPolicy(CreationPolicy.Shared)]
internal class DefaultKeyBindingManager : IKeyBindingManager
{
private readonly string jsonConfigFilePath;

private class Config
{
public Dictionary<string, string> KeyBindings { get; set; } = new();
}

public IEnumerable<KeyBindingDefinition> KeyBindingDefinations => definitionMap.Values;

private Dictionary<string, KeyBindingDefinition> definitionMap = new();

[ImportingConstructor]
public DefaultKeyBindingManager([ImportMany] KeyBindingDefinition[] definations)
{
definitionMap = definations.ToDictionary(x => x.ConfigKey, x => x);

jsonConfigFilePath = Path.GetFullPath("./keybind.json");
Log.LogInfo($"jsonConfigFilePath: {jsonConfigFilePath}");

LoadConfig();
}

public void SaveConfig()
{
var json = JsonSerializer.Serialize(new Config() { KeyBindings = definitionMap.ToDictionary(x => x.Key, x => KeyBindingDefinition.FormatToExpression(x.Value.Key, x.Value.Modifiers)) });
File.WriteAllText(jsonConfigFilePath, json);

Log.LogInfo($"Saved.");
}

public void LoadConfig()
{
if (File.Exists(jsonConfigFilePath))
{
try
{
var json = File.ReadAllText(jsonConfigFilePath);
var strMap = JsonSerializer.Deserialize<Config>(json).KeyBindings;

foreach (var item in strMap)
{
var name = item.Key;
var expr = item.Value;

if (!KeyBindingDefinition.TryParseExpression(expr, out var k, out var m))
{
Log.LogError($"Can't parse {name} keybinding expr: {expr}");
continue;
}

if (definitionMap.TryGetValue(name, out var definition))
{
definition.Key = k;
definition.Modifiers = m;
}
}
}
catch (Exception e)
{
Log.LogInfo($"Load failed: {e.Message}");
}
}

Log.LogInfo($"Loaded.");
}

public bool CheckKeyBinding(KeyBindingDefinition defination, KeyEventArgs e)
{
var key = (e.Key == Key.System) ? e.SystemKey : e.Key;

if (defination.Key == Key.None)
return false;

var modifier = Keyboard.Modifiers;
#if DEBUG
var str = $"{KeyBindingDefinition.FormatToExpression(key, modifier)} check {defination.Name}({KeyBindingDefinition.FormatToExpression(defination)})";
if (QueryKeyBinding(key, modifier) is KeyBindingDefinition query)
str += $", query {query.Name}({KeyBindingDefinition.FormatToExpression(query)})";
Log.LogDebug(str);
#endif
return (key == defination.Key) && (modifier == GetActualModifiers(e.Key, defination.Modifiers));
}

private static ModifierKeys GetActualModifiers(Key key, ModifierKeys modifiers)
{
switch (key)
{
case Key.LeftCtrl:
case Key.RightCtrl:
modifiers |= ModifierKeys.Control;
return modifiers;

case Key.LeftAlt:
case Key.RightAlt:
modifiers |= ModifierKeys.Alt;
return modifiers;

case Key.LeftShift:
case Key.RightShift:
modifiers |= ModifierKeys.Shift;
break;
}

return modifiers;
}

public void ChangeKeyBinding(KeyBindingDefinition definition, Key newKey, ModifierKeys newModifier)
{
Log.LogInfo($"{KeyBindingDefinition.FormatToExpression(definition.Key, definition.Modifiers)} --> {KeyBindingDefinition.FormatToExpression(newKey, newModifier)}");

definition.Key = newKey;
definition.Modifiers = newModifier;
}

public KeyBindingDefinition QueryKeyBinding(Key key, ModifierKeys modifier)
{
if (key is Key.None)
return default;

return KeyBindingDefinations.FirstOrDefault(x => x.Key == key && modifier == x.Modifiers);
}
}
}
27 changes: 27 additions & 0 deletions OngekiFumenEditor/Kernel/KeyBinding/IKeyBindingManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace OngekiFumenEditor.Kernel.KeyBinding
{
internal interface IKeyBindingManager
{
bool CheckKeyBinding(KeyBindingDefinition defination, KeyEventArgs e);

void ChangeKeyBinding(KeyBindingDefinition definition, Key newKey, ModifierKeys newModifier);

void DefaultKeyBinding(KeyBindingDefinition definition) =>
ChangeKeyBinding(definition, definition.DefaultKey, definition.DefaultModifiers);

KeyBindingDefinition QueryKeyBinding(Key key, ModifierKeys modifier);

void SaveConfig();

void LoadConfig();

IEnumerable<KeyBindingDefinition> KeyBindingDefinations { get; }
}
}
117 changes: 117 additions & 0 deletions OngekiFumenEditor/Kernel/KeyBinding/KeyBindingDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Caliburn.Micro;
using OngekiFumenEditor.Properties;
using System.Text.RegularExpressions;
using System;
using System.Windows.Input;
using Xceed.Wpf.Toolkit.Core.Input;

namespace OngekiFumenEditor.Kernel.KeyBinding
{
public class KeyBindingDefinition : PropertyChangedBase
{
private readonly string resourceName;

public Key DefaultKey { get; }
public ModifierKeys DefaultModifiers { get; }

public string ConfigKey => resourceName;

public string Name => resourceName/*Resources.ResourceManager.GetString(resourceName)*/;

public KeyBindingDefinition(string resourceName, Key defaultKey) : this(resourceName, ModifierKeys.None, defaultKey)
{

}

public KeyBindingDefinition(string resourceName, ModifierKeys defaultModifiers, Key defaultKey)
{
this.resourceName = resourceName;

DefaultModifiers = defaultModifiers;
DefaultKey = defaultKey;
}

private Key? key;
public Key Key
{
get => key ?? DefaultKey;
set
{
Set(ref key, value);
}
}

private ModifierKeys? modifiers;
public ModifierKeys Modifiers
{
get => modifiers ?? DefaultModifiers;
set
{
Set(ref modifiers, value);
}
}

public static string FormatToExpression(Key key, ModifierKeys modifier)
{
var modifierStr = modifier switch
{
ModifierKeys.Alt => "Alt",
ModifierKeys.Control => "Ctrl",
ModifierKeys.Shift => "Shift",
ModifierKeys.Windows => "Win",
_ => string.Empty,
};

var expr = key is Key.None ? string.Empty : key.ToString();

if (!string.IsNullOrWhiteSpace(modifierStr))
expr = modifierStr + " + " + expr;

return expr;
}

public static string FormatToExpression(KeyBindingDefinition definition)
{
return FormatToExpression(definition.Key, definition.Modifiers);
}

//Ctrl + A
static Regex regex = new Regex(@"(\s*\w+\s*\+\s*)?(\w+)");

public static bool TryParseExpression(string keybindExpr, out Key key, out ModifierKeys modifier)
{
key = Key.None;
modifier = ModifierKeys.None;

if (string.IsNullOrWhiteSpace(keybindExpr))
return true;

var match = regex.Match(keybindExpr);
if (!match.Success)
return false;

var modifierStr = match.Groups[1].Value.Trim().ToLower().TrimEnd('+').Trim();
if (!string.IsNullOrWhiteSpace(modifierStr))
{
modifier = modifierStr switch
{
"ctrl" or "control" => ModifierKeys.Control,
"win" or "windows" => ModifierKeys.Windows,
"alt" => ModifierKeys.Alt,
"shift" => ModifierKeys.Shift,
_ => ModifierKeys.None
};

if (modifier == ModifierKeys.None)
return false;
}

var keyStr = match.Groups[2].Value.Trim();
if (!Enum.TryParse<Key>(keyStr, true, out var k))
return false;

key = k;
return key != Key.None;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<mah:MetroWindow
x:Class="OngekiFumenEditor.Kernel.SettingPages.KeyBinding.Dialogs.ConfigKeyBindingDialog"
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:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="设置快捷键"
Width="300"
Background="{DynamicResource EnvironmentWindowBackground}"
SaveWindowPosition="True"
ShowMaxRestoreButton="False"
ShowMinButton="False"
SizeToContent="WidthAndHeight"
Style="{DynamicResource MainWindowStyle}"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Window.DataContext>
<Binding RelativeSource="{RelativeSource Self}">
</Binding>
</Window.DataContext>
<StackPanel Margin="10">
<TextBlock
HorizontalAlignment="Center"
FontSize="16"
Text="{Binding Definition.Name, StringFormat=请为 {0} 输入合适的快捷键组合:}" />
<TextBlock
Margin="0,15"
HorizontalAlignment="Center"
FontSize="25"
FontWeight="Bold"
Text="{Binding CurrentExpression}">
</TextBlock>
<TextBlock
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold"
Text="{Binding ConflictDefinition.Name, StringFormat=与 {0} 的绑定键位起冲突}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Button
Margin="5"
Padding="10,5,10,5"
Click="Button_Click_1"
FontSize="16">
确定
</Button>

<Button
Grid.Column="1"
Margin="5"
Padding="10,5,10,5"
Click="Button_Click"
FontSize="16">
重置
</Button>

<Button
Grid.Column="2"
Margin="5"
Padding="10,5,10,5"
Click="Button_Click_2"
FontSize="16">
清空绑定
</Button>
</Grid>
</StackPanel>
</mah:MetroWindow>
Loading

0 comments on commit 98555a6

Please sign in to comment.