Skip to content

Commit 98555a6

Browse files
committed
implement KeyBinding
1 parent f808871 commit 98555a6

16 files changed

+880
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using OngekiFumenEditor.Properties;
2+
using OngekiFumenEditor.Utils;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.ComponentModel.Composition;
6+
using System.Configuration;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Text.Json;
11+
using System.Text.RegularExpressions;
12+
using System.Threading.Tasks;
13+
using System.Windows.Input;
14+
15+
namespace OngekiFumenEditor.Kernel.KeyBinding
16+
{
17+
[Export(typeof(IKeyBindingManager))]
18+
[PartCreationPolicy(CreationPolicy.Shared)]
19+
internal class DefaultKeyBindingManager : IKeyBindingManager
20+
{
21+
private readonly string jsonConfigFilePath;
22+
23+
private class Config
24+
{
25+
public Dictionary<string, string> KeyBindings { get; set; } = new();
26+
}
27+
28+
public IEnumerable<KeyBindingDefinition> KeyBindingDefinations => definitionMap.Values;
29+
30+
private Dictionary<string, KeyBindingDefinition> definitionMap = new();
31+
32+
[ImportingConstructor]
33+
public DefaultKeyBindingManager([ImportMany] KeyBindingDefinition[] definations)
34+
{
35+
definitionMap = definations.ToDictionary(x => x.ConfigKey, x => x);
36+
37+
jsonConfigFilePath = Path.GetFullPath("./keybind.json");
38+
Log.LogInfo($"jsonConfigFilePath: {jsonConfigFilePath}");
39+
40+
LoadConfig();
41+
}
42+
43+
public void SaveConfig()
44+
{
45+
var json = JsonSerializer.Serialize(new Config() { KeyBindings = definitionMap.ToDictionary(x => x.Key, x => KeyBindingDefinition.FormatToExpression(x.Value.Key, x.Value.Modifiers)) });
46+
File.WriteAllText(jsonConfigFilePath, json);
47+
48+
Log.LogInfo($"Saved.");
49+
}
50+
51+
public void LoadConfig()
52+
{
53+
if (File.Exists(jsonConfigFilePath))
54+
{
55+
try
56+
{
57+
var json = File.ReadAllText(jsonConfigFilePath);
58+
var strMap = JsonSerializer.Deserialize<Config>(json).KeyBindings;
59+
60+
foreach (var item in strMap)
61+
{
62+
var name = item.Key;
63+
var expr = item.Value;
64+
65+
if (!KeyBindingDefinition.TryParseExpression(expr, out var k, out var m))
66+
{
67+
Log.LogError($"Can't parse {name} keybinding expr: {expr}");
68+
continue;
69+
}
70+
71+
if (definitionMap.TryGetValue(name, out var definition))
72+
{
73+
definition.Key = k;
74+
definition.Modifiers = m;
75+
}
76+
}
77+
}
78+
catch (Exception e)
79+
{
80+
Log.LogInfo($"Load failed: {e.Message}");
81+
}
82+
}
83+
84+
Log.LogInfo($"Loaded.");
85+
}
86+
87+
public bool CheckKeyBinding(KeyBindingDefinition defination, KeyEventArgs e)
88+
{
89+
var key = (e.Key == Key.System) ? e.SystemKey : e.Key;
90+
91+
if (defination.Key == Key.None)
92+
return false;
93+
94+
var modifier = Keyboard.Modifiers;
95+
#if DEBUG
96+
var str = $"{KeyBindingDefinition.FormatToExpression(key, modifier)} check {defination.Name}({KeyBindingDefinition.FormatToExpression(defination)})";
97+
if (QueryKeyBinding(key, modifier) is KeyBindingDefinition query)
98+
str += $", query {query.Name}({KeyBindingDefinition.FormatToExpression(query)})";
99+
Log.LogDebug(str);
100+
#endif
101+
return (key == defination.Key) && (modifier == GetActualModifiers(e.Key, defination.Modifiers));
102+
}
103+
104+
private static ModifierKeys GetActualModifiers(Key key, ModifierKeys modifiers)
105+
{
106+
switch (key)
107+
{
108+
case Key.LeftCtrl:
109+
case Key.RightCtrl:
110+
modifiers |= ModifierKeys.Control;
111+
return modifiers;
112+
113+
case Key.LeftAlt:
114+
case Key.RightAlt:
115+
modifiers |= ModifierKeys.Alt;
116+
return modifiers;
117+
118+
case Key.LeftShift:
119+
case Key.RightShift:
120+
modifiers |= ModifierKeys.Shift;
121+
break;
122+
}
123+
124+
return modifiers;
125+
}
126+
127+
public void ChangeKeyBinding(KeyBindingDefinition definition, Key newKey, ModifierKeys newModifier)
128+
{
129+
Log.LogInfo($"{KeyBindingDefinition.FormatToExpression(definition.Key, definition.Modifiers)} --> {KeyBindingDefinition.FormatToExpression(newKey, newModifier)}");
130+
131+
definition.Key = newKey;
132+
definition.Modifiers = newModifier;
133+
}
134+
135+
public KeyBindingDefinition QueryKeyBinding(Key key, ModifierKeys modifier)
136+
{
137+
if (key is Key.None)
138+
return default;
139+
140+
return KeyBindingDefinations.FirstOrDefault(x => x.Key == key && modifier == x.Modifiers);
141+
}
142+
}
143+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Windows.Input;
7+
8+
namespace OngekiFumenEditor.Kernel.KeyBinding
9+
{
10+
internal interface IKeyBindingManager
11+
{
12+
bool CheckKeyBinding(KeyBindingDefinition defination, KeyEventArgs e);
13+
14+
void ChangeKeyBinding(KeyBindingDefinition definition, Key newKey, ModifierKeys newModifier);
15+
16+
void DefaultKeyBinding(KeyBindingDefinition definition) =>
17+
ChangeKeyBinding(definition, definition.DefaultKey, definition.DefaultModifiers);
18+
19+
KeyBindingDefinition QueryKeyBinding(Key key, ModifierKeys modifier);
20+
21+
void SaveConfig();
22+
23+
void LoadConfig();
24+
25+
IEnumerable<KeyBindingDefinition> KeyBindingDefinations { get; }
26+
}
27+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using Caliburn.Micro;
2+
using OngekiFumenEditor.Properties;
3+
using System.Text.RegularExpressions;
4+
using System;
5+
using System.Windows.Input;
6+
using Xceed.Wpf.Toolkit.Core.Input;
7+
8+
namespace OngekiFumenEditor.Kernel.KeyBinding
9+
{
10+
public class KeyBindingDefinition : PropertyChangedBase
11+
{
12+
private readonly string resourceName;
13+
14+
public Key DefaultKey { get; }
15+
public ModifierKeys DefaultModifiers { get; }
16+
17+
public string ConfigKey => resourceName;
18+
19+
public string Name => resourceName/*Resources.ResourceManager.GetString(resourceName)*/;
20+
21+
public KeyBindingDefinition(string resourceName, Key defaultKey) : this(resourceName, ModifierKeys.None, defaultKey)
22+
{
23+
24+
}
25+
26+
public KeyBindingDefinition(string resourceName, ModifierKeys defaultModifiers, Key defaultKey)
27+
{
28+
this.resourceName = resourceName;
29+
30+
DefaultModifiers = defaultModifiers;
31+
DefaultKey = defaultKey;
32+
}
33+
34+
private Key? key;
35+
public Key Key
36+
{
37+
get => key ?? DefaultKey;
38+
set
39+
{
40+
Set(ref key, value);
41+
}
42+
}
43+
44+
private ModifierKeys? modifiers;
45+
public ModifierKeys Modifiers
46+
{
47+
get => modifiers ?? DefaultModifiers;
48+
set
49+
{
50+
Set(ref modifiers, value);
51+
}
52+
}
53+
54+
public static string FormatToExpression(Key key, ModifierKeys modifier)
55+
{
56+
var modifierStr = modifier switch
57+
{
58+
ModifierKeys.Alt => "Alt",
59+
ModifierKeys.Control => "Ctrl",
60+
ModifierKeys.Shift => "Shift",
61+
ModifierKeys.Windows => "Win",
62+
_ => string.Empty,
63+
};
64+
65+
var expr = key is Key.None ? string.Empty : key.ToString();
66+
67+
if (!string.IsNullOrWhiteSpace(modifierStr))
68+
expr = modifierStr + " + " + expr;
69+
70+
return expr;
71+
}
72+
73+
public static string FormatToExpression(KeyBindingDefinition definition)
74+
{
75+
return FormatToExpression(definition.Key, definition.Modifiers);
76+
}
77+
78+
//Ctrl + A
79+
static Regex regex = new Regex(@"(\s*\w+\s*\+\s*)?(\w+)");
80+
81+
public static bool TryParseExpression(string keybindExpr, out Key key, out ModifierKeys modifier)
82+
{
83+
key = Key.None;
84+
modifier = ModifierKeys.None;
85+
86+
if (string.IsNullOrWhiteSpace(keybindExpr))
87+
return true;
88+
89+
var match = regex.Match(keybindExpr);
90+
if (!match.Success)
91+
return false;
92+
93+
var modifierStr = match.Groups[1].Value.Trim().ToLower().TrimEnd('+').Trim();
94+
if (!string.IsNullOrWhiteSpace(modifierStr))
95+
{
96+
modifier = modifierStr switch
97+
{
98+
"ctrl" or "control" => ModifierKeys.Control,
99+
"win" or "windows" => ModifierKeys.Windows,
100+
"alt" => ModifierKeys.Alt,
101+
"shift" => ModifierKeys.Shift,
102+
_ => ModifierKeys.None
103+
};
104+
105+
if (modifier == ModifierKeys.None)
106+
return false;
107+
}
108+
109+
var keyStr = match.Groups[2].Value.Trim();
110+
if (!Enum.TryParse<Key>(keyStr, true, out var k))
111+
return false;
112+
113+
key = k;
114+
return key != Key.None;
115+
}
116+
}
117+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<mah:MetroWindow
2+
x:Class="OngekiFumenEditor.Kernel.SettingPages.KeyBinding.Dialogs.ConfigKeyBindingDialog"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
Title="设置快捷键"
9+
Width="300"
10+
Background="{DynamicResource EnvironmentWindowBackground}"
11+
SaveWindowPosition="True"
12+
ShowMaxRestoreButton="False"
13+
ShowMinButton="False"
14+
SizeToContent="WidthAndHeight"
15+
Style="{DynamicResource MainWindowStyle}"
16+
WindowStartupLocation="CenterOwner"
17+
mc:Ignorable="d">
18+
<Window.DataContext>
19+
<Binding RelativeSource="{RelativeSource Self}">
20+
</Binding>
21+
</Window.DataContext>
22+
<StackPanel Margin="10">
23+
<TextBlock
24+
HorizontalAlignment="Center"
25+
FontSize="16"
26+
Text="{Binding Definition.Name, StringFormat=请为 {0} 输入合适的快捷键组合:}" />
27+
<TextBlock
28+
Margin="0,15"
29+
HorizontalAlignment="Center"
30+
FontSize="25"
31+
FontWeight="Bold"
32+
Text="{Binding CurrentExpression}">
33+
</TextBlock>
34+
<TextBlock
35+
HorizontalAlignment="Center"
36+
FontSize="16"
37+
FontWeight="Bold"
38+
Text="{Binding ConflictDefinition.Name, StringFormat=与 {0} 的绑定键位起冲突}" />
39+
<Grid>
40+
<Grid.ColumnDefinitions>
41+
<ColumnDefinition Width="1*" />
42+
<ColumnDefinition Width="1*" />
43+
<ColumnDefinition Width="1*" />
44+
</Grid.ColumnDefinitions>
45+
<Button
46+
Margin="5"
47+
Padding="10,5,10,5"
48+
Click="Button_Click_1"
49+
FontSize="16">
50+
确定
51+
</Button>
52+
53+
<Button
54+
Grid.Column="1"
55+
Margin="5"
56+
Padding="10,5,10,5"
57+
Click="Button_Click"
58+
FontSize="16">
59+
重置
60+
</Button>
61+
62+
<Button
63+
Grid.Column="2"
64+
Margin="5"
65+
Padding="10,5,10,5"
66+
Click="Button_Click_2"
67+
FontSize="16">
68+
清空绑定
69+
</Button>
70+
</Grid>
71+
</StackPanel>
72+
</mah:MetroWindow>

0 commit comments

Comments
 (0)