Added Macro functionality, udpated readme
This commit is contained in:
+421
-121
@@ -7,7 +7,7 @@
|
||||
// Host Command → Zahlen-Eingabe (Command-ID)
|
||||
// Keine Aktion → nichts
|
||||
//
|
||||
// Optional (nur MX-Buttons): LED-Basisfarbe
|
||||
// Optional (nur MX-Buttons): LED-Basisfarbe + LED-Animation + Geschwindigkeit
|
||||
|
||||
namespace VersaGUI;
|
||||
|
||||
@@ -16,6 +16,8 @@ public class ActionDialog : Form
|
||||
// ── Ergebnis ──────────────────────────────────────────────────────────────
|
||||
public DeviceAction ResultAction { get; private set; }
|
||||
public Color ResultColor { get; private set; }
|
||||
public LedAnimType ResultAnim { get; private set; }
|
||||
public ushort ResultPeriod { get; private set; }
|
||||
|
||||
// ── Controls ──────────────────────────────────────────────────────────────
|
||||
private readonly ComboBox _typeCombo;
|
||||
@@ -38,86 +40,132 @@ public class ActionDialog : Form
|
||||
private readonly Button _colorBtn;
|
||||
private Color _color;
|
||||
|
||||
// LED-Animation
|
||||
private readonly Panel _animPanel;
|
||||
private readonly ComboBox _animCombo;
|
||||
private readonly ComboBox _periodCombo;
|
||||
|
||||
// Makro
|
||||
private readonly Panel _macroPanel;
|
||||
private readonly MacroTable _macros;
|
||||
private readonly int _macroSlot;
|
||||
// Je Step: [CaptureBtn, CheckCtrl, CheckShift, CheckAlt]
|
||||
private readonly Button[] _stepBtns = new Button[4];
|
||||
private readonly CheckBox[] _stepCtrl = new CheckBox[4];
|
||||
private readonly CheckBox[] _stepShift = new CheckBox[4];
|
||||
private readonly CheckBox[] _stepAlt = new CheckBox[4];
|
||||
private readonly byte[] _stepKeycodes = new byte[4];
|
||||
private int _captureStep = -1; // -1 = nicht im Capture-Modus
|
||||
|
||||
// Capture-Zustand
|
||||
private bool _capturing;
|
||||
private byte _hidKeycode; // 0 = nicht belegt
|
||||
|
||||
// Layout
|
||||
private readonly bool _showColor;
|
||||
private Button _okBtn = null!;
|
||||
private Button _cancelBtn = null!;
|
||||
|
||||
// ── Lookup-Tabellen ───────────────────────────────────────────────────────
|
||||
|
||||
// Windows VK → HID Keyboard Usage (USB HID Usage Table 0x07)
|
||||
private static readonly Dictionary<Keys, byte> s_vkToHid = new()
|
||||
// P/Invoke: Windows-API für layout-unabhängige Scan-Code-Konvertierung
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll",
|
||||
CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
|
||||
private static extern int GetKeyNameText(int lParam,
|
||||
System.Text.StringBuilder lpString, int nSize);
|
||||
|
||||
// Scan-Code (PS/2 Set 1) → HID Usage (physische Tastenposition, Layout-unabhängig).
|
||||
// Korrekt für QWERTZ, AZERTY und andere Layouts: Ö = Scan 0x27 → HID 0x33 (;-Position)
|
||||
private static readonly Dictionary<byte, byte> s_scanToHid = new()
|
||||
{
|
||||
// Buchstaben
|
||||
[Keys.A]=0x04,[Keys.B]=0x05,[Keys.C]=0x06,[Keys.D]=0x07,
|
||||
[Keys.E]=0x08,[Keys.F]=0x09,[Keys.G]=0x0A,[Keys.H]=0x0B,
|
||||
[Keys.I]=0x0C,[Keys.J]=0x0D,[Keys.K]=0x0E,[Keys.L]=0x0F,
|
||||
[Keys.M]=0x10,[Keys.N]=0x11,[Keys.O]=0x12,[Keys.P]=0x13,
|
||||
[Keys.Q]=0x14,[Keys.R]=0x15,[Keys.S]=0x16,[Keys.T]=0x17,
|
||||
[Keys.U]=0x18,[Keys.V]=0x19,[Keys.W]=0x1A,[Keys.X]=0x1B,
|
||||
[Keys.Y]=0x1C,[Keys.Z]=0x1D,
|
||||
// Ziffernreihe
|
||||
[Keys.D1]=0x1E,[Keys.D2]=0x1F,[Keys.D3]=0x20,[Keys.D4]=0x21,
|
||||
[Keys.D5]=0x22,[Keys.D6]=0x23,[Keys.D7]=0x24,[Keys.D8]=0x25,
|
||||
[Keys.D9]=0x26,[Keys.D0]=0x27,
|
||||
// Steuerung
|
||||
[Keys.Return]=0x28,[Keys.Escape]=0x29,[Keys.Back]=0x2A,
|
||||
[Keys.Tab]=0x2B, [Keys.Space]=0x2C, [Keys.Delete]=0x4C,
|
||||
[Keys.Insert]=0x49,[Keys.Home]=0x4A, [Keys.PageUp]=0x4B,
|
||||
[Keys.End]=0x4D, [Keys.PageDown]=0x4E,
|
||||
[Keys.Right]=0x4F,[Keys.Left]=0x50, [Keys.Down]=0x51,[Keys.Up]=0x52,
|
||||
[Keys.CapsLock]=0x39,
|
||||
[Keys.PrintScreen]=0x46,[Keys.Scroll]=0x47,[Keys.Pause]=0x48,
|
||||
[Keys.NumLock]=0x53,
|
||||
// F-Tasten
|
||||
[Keys.F1]=0x3A,[Keys.F2]=0x3B,[Keys.F3]=0x3C,[Keys.F4]=0x3D,
|
||||
[Keys.F5]=0x3E,[Keys.F6]=0x3F,[Keys.F7]=0x40,[Keys.F8]=0x41,
|
||||
[Keys.F9]=0x42,[Keys.F10]=0x43,[Keys.F11]=0x44,[Keys.F12]=0x45,
|
||||
// Sonderzeichen (QWERTZ-kompatibel, basiert auf US-HID-Positionen)
|
||||
[Keys.OemMinus]=0x2D, [Keys.Oemplus]=0x2E,
|
||||
[Keys.OemOpenBrackets]=0x2F, [Keys.OemCloseBrackets]=0x30,
|
||||
[Keys.OemPipe]=0x31, [Keys.OemSemicolon]=0x33,
|
||||
[Keys.OemQuotes]=0x34, [Keys.Oemtilde]=0x35,
|
||||
[Keys.Oemcomma]=0x36, [Keys.OemPeriod]=0x37,
|
||||
[Keys.OemQuestion]=0x38,
|
||||
// Numpad
|
||||
[Keys.NumPad1]=0x59,[Keys.NumPad2]=0x5A,[Keys.NumPad3]=0x5B,
|
||||
[Keys.NumPad4]=0x5C,[Keys.NumPad5]=0x5D,[Keys.NumPad6]=0x5E,
|
||||
[Keys.NumPad7]=0x5F,[Keys.NumPad8]=0x60,[Keys.NumPad9]=0x61,
|
||||
[Keys.NumPad0]=0x62,[Keys.Decimal]=0x63,
|
||||
[Keys.Multiply]=0x55,[Keys.Add]=0x57,
|
||||
[Keys.Subtract]=0x56,[Keys.Divide]=0x54,
|
||||
[0x01]=0x29, // Escape
|
||||
[0x02]=0x1E,[0x03]=0x1F,[0x04]=0x20,[0x05]=0x21,[0x06]=0x22,
|
||||
[0x07]=0x23,[0x08]=0x24,[0x09]=0x25,[0x0A]=0x26,[0x0B]=0x27, // 1–0
|
||||
[0x0C]=0x2D,[0x0D]=0x2E, // -/ß =/´
|
||||
[0x0E]=0x2A, // Backspace
|
||||
[0x0F]=0x2B, // Tab
|
||||
[0x10]=0x14,[0x11]=0x1A,[0x12]=0x08,[0x13]=0x15,[0x14]=0x17, // Q W E R T
|
||||
[0x15]=0x1C,[0x16]=0x18,[0x17]=0x0C,[0x18]=0x12,[0x19]=0x13, // Z/Y U I O P
|
||||
[0x1A]=0x2F,[0x1B]=0x30, // Ü +
|
||||
[0x1C]=0x28, // Enter
|
||||
[0x1E]=0x04,[0x1F]=0x16,[0x20]=0x07,[0x21]=0x09,[0x22]=0x0A, // A S D F G
|
||||
[0x23]=0x0B,[0x24]=0x0D,[0x25]=0x0E,[0x26]=0x0F, // H J K L
|
||||
[0x27]=0x33,[0x28]=0x34,[0x29]=0x35, // Ö Ä ^
|
||||
[0x2B]=0x31, // #
|
||||
[0x2C]=0x1D,[0x2D]=0x1B,[0x2E]=0x06,[0x2F]=0x19,[0x30]=0x05, // Y/Z X C V B
|
||||
[0x31]=0x11,[0x32]=0x10,[0x33]=0x36,[0x34]=0x37,[0x35]=0x38, // N M , . -
|
||||
[0x37]=0x55, // Numpad *
|
||||
[0x39]=0x2C, // Space
|
||||
[0x3A]=0x39, // CapsLock
|
||||
[0x3B]=0x3A,[0x3C]=0x3B,[0x3D]=0x3C,[0x3E]=0x3D,[0x3F]=0x3E, // F1–F5
|
||||
[0x40]=0x3F,[0x41]=0x40,[0x42]=0x41,[0x43]=0x42,[0x44]=0x43, // F6–F10
|
||||
[0x45]=0x53,[0x46]=0x47, // NumLock ScrollLock
|
||||
[0x47]=0x5F,[0x48]=0x60,[0x49]=0x61,[0x4A]=0x56, // Num7 8 9 -
|
||||
[0x4B]=0x5C,[0x4C]=0x5D,[0x4D]=0x5E,[0x4E]=0x57, // Num4 5 6 +
|
||||
[0x4F]=0x59,[0x50]=0x5A,[0x51]=0x5B,[0x52]=0x62,[0x53]=0x63, // Num1 2 3 0 .
|
||||
[0x56]=0x64, // Non-US \ (< > auf QWERTZ)
|
||||
[0x57]=0x44,[0x58]=0x45, // F11 F12
|
||||
};
|
||||
|
||||
// HID-Code → lesbarer Name (für Capture-Button-Beschriftung)
|
||||
private static readonly Dictionary<byte, string> s_hidNames = new()
|
||||
// HID → Scan-Code (umgekehrte Tabelle, für Anzeigenamen)
|
||||
private static readonly Dictionary<byte, byte> s_hidToScan =
|
||||
s_scanToHid.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
|
||||
// Direkte VK→HID-Zuordnung nur für erweiterte Navigationstasten.
|
||||
// MapVirtualKey gibt für diese den Numpad-Scan-Code zurück (falsch).
|
||||
private static readonly Dictionary<Keys, byte> s_extVkToHid = new()
|
||||
{
|
||||
[0x04]="A",[0x05]="B",[0x06]="C",[0x07]="D",[0x08]="E",[0x09]="F",
|
||||
[0x0A]="G",[0x0B]="H",[0x0C]="I",[0x0D]="J",[0x0E]="K",[0x0F]="L",
|
||||
[0x10]="M",[0x11]="N",[0x12]="O",[0x13]="P",[0x14]="Q",[0x15]="R",
|
||||
[0x16]="S",[0x17]="T",[0x18]="U",[0x19]="V",[0x1A]="W",[0x1B]="X",
|
||||
[0x1C]="Y",[0x1D]="Z",
|
||||
[0x1E]="1",[0x1F]="2",[0x20]="3",[0x21]="4",[0x22]="5",
|
||||
[0x23]="6",[0x24]="7",[0x25]="8",[0x26]="9",[0x27]="0",
|
||||
[0x28]="Enter", [0x29]="Escape", [0x2A]="Backspace",
|
||||
[0x2B]="Tab", [0x2C]="Leertaste", [0x2D]="-",
|
||||
[0x2E]="=", [0x2F]="[", [0x30]="]",
|
||||
[0x31]="\\", [0x33]=";", [0x34]="'",
|
||||
[0x35]="`", [0x36]=",", [0x37]=".",
|
||||
[0x38]="/", [0x39]="CapsLock",
|
||||
[0x3A]="F1", [0x3B]="F2", [0x3C]="F3", [0x3D]="F4",
|
||||
[0x3E]="F5", [0x3F]="F6", [0x40]="F7", [0x41]="F8",
|
||||
[0x42]="F9", [0x43]="F10",[0x44]="F11",[0x45]="F12",
|
||||
[0x46]="Druck", [0x47]="Rollen", [0x48]="Pause",
|
||||
[0x49]="Einfg", [0x4A]="Pos1", [0x4B]="Bild Auf",
|
||||
[0x4C]="Entf", [0x4D]="Ende", [0x4E]="Bild Ab",
|
||||
[0x4F]="Rechts", [0x50]="Links", [0x51]="Runter", [0x52]="Hoch",
|
||||
[0x53]="NumLock",[0x54]="Num/", [0x55]="Num*",
|
||||
[0x56]="Num-", [0x57]="Num+",
|
||||
[0x59]="Num1", [0x5A]="Num2", [0x5B]="Num3", [0x5C]="Num4",
|
||||
[0x5D]="Num5", [0x5E]="Num6", [0x5F]="Num7", [0x60]="Num8",
|
||||
[0x61]="Num9", [0x62]="Num0", [0x63]="Num.",
|
||||
[Keys.Up]=0x52, [Keys.Down]=0x51, [Keys.Left]=0x50,
|
||||
[Keys.Right]=0x4F, [Keys.Home]=0x4A, [Keys.End]=0x4D,
|
||||
[Keys.PageUp]=0x4B, [Keys.PageDown]=0x4E, [Keys.Insert]=0x49,
|
||||
[Keys.Delete]=0x4C, [Keys.Pause]=0x48, [Keys.Scroll]=0x47,
|
||||
[Keys.PrintScreen]=0x46,
|
||||
};
|
||||
|
||||
// Windows VK → HID via Scan-Code (layout-unabhängig).
|
||||
// Ö auf QWERTZ → Scan 0x27 → HID 0x33 (;-Position auf US) → Board sendet HID 0x33
|
||||
// → Zielrechner mit QWERTZ-Layout erzeugt korrekt Ö.
|
||||
private static byte VkToHid(Keys vk)
|
||||
{
|
||||
if (s_extVkToHid.TryGetValue(vk, out byte hExt)) return hExt;
|
||||
byte sc = (byte)MapVirtualKey((uint)(vk & Keys.KeyCode), 0u /* VK→SC */);
|
||||
return s_scanToHid.TryGetValue(sc, out byte hid) ? hid : (byte)0;
|
||||
}
|
||||
|
||||
// HID-Code → lesbarer Tastenname laut aktivem Windows-Tastaturlayout.
|
||||
// Gibt auf QWERTZ "Ö" für HID 0x33, auf US-Layout ";" zurück.
|
||||
private static string HidKeyName(byte hid)
|
||||
{
|
||||
// Sondertasten ohne Scan-Code-Eintrag
|
||||
var special = (Dictionary<byte, string>)new Dictionary<byte, string>
|
||||
{
|
||||
[0x28]="Enter", [0x29]="Escape", [0x2A]="Backspace", [0x2B]="Tab",
|
||||
[0x2C]="Leer", [0x39]="Caps", [0x46]="Druck", [0x47]="Rollen",
|
||||
[0x48]="Pause", [0x49]="Einfg", [0x4A]="Pos1", [0x4B]="Bild↑",
|
||||
[0x4C]="Entf", [0x4D]="Ende", [0x4E]="Bild↓",
|
||||
[0x4F]="→", [0x50]="←", [0x51]="↓", [0x52]="↑",
|
||||
[0x53]="NumLock",[0x54]="Num/", [0x55]="Num*", [0x56]="Num-",
|
||||
[0x57]="Num+", [0x58]="NumEnter",
|
||||
[0x59]="Num1", [0x5A]="Num2", [0x5B]="Num3", [0x5C]="Num4",
|
||||
[0x5D]="Num5", [0x5E]="Num6", [0x5F]="Num7", [0x60]="Num8",
|
||||
[0x61]="Num9", [0x62]="Num0", [0x63]="Num.",
|
||||
[0x3A]="F1",[0x3B]="F2",[0x3C]="F3",[0x3D]="F4",[0x3E]="F5",
|
||||
[0x3F]="F6",[0x40]="F7",[0x41]="F8",[0x42]="F9",[0x43]="F10",
|
||||
[0x44]="F11",[0x45]="F12",
|
||||
};
|
||||
if (special.TryGetValue(hid, out string? name)) return name;
|
||||
|
||||
// Für Zeichen-Tasten: GetKeyNameText gibt den lokalisierten Namen zurück
|
||||
if (!s_hidToScan.TryGetValue(hid, out byte sc)) return $"0x{hid:X2}";
|
||||
int lParam = sc << 16;
|
||||
var sb = new System.Text.StringBuilder(32);
|
||||
GetKeyNameText(lParam, sb, sb.Capacity);
|
||||
return sb.Length > 0 ? sb.ToString() : $"0x{hid:X2}";
|
||||
}
|
||||
|
||||
// HID Consumer Usage IDs (Usage Page 0x0C)
|
||||
private static readonly (ushort Usage, string Name)[] s_consumer =
|
||||
{
|
||||
@@ -137,11 +185,43 @@ public class ActionDialog : Form
|
||||
|
||||
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||||
|
||||
public ActionDialog(DeviceAction action, Color ledColor, bool showColor = true)
|
||||
// Animations-ComboBox-Einträge: (Anzeigename, LedAnimType, Farbe sinnvoll?)
|
||||
private static readonly (string Name, LedAnimType Type, bool ShowColor)[] s_anims =
|
||||
{
|
||||
("Statisch", LedAnimType.Static, true),
|
||||
("Blinken", LedAnimType.Blink, true),
|
||||
("Pulsieren", LedAnimType.Pulse, true),
|
||||
("Regenbogen", LedAnimType.ColorCycle, false),
|
||||
};
|
||||
|
||||
// Geschwindigkeits-Presets: (Anzeigename, Millisekunden)
|
||||
private static readonly (string Name, ushort Ms)[] s_periods =
|
||||
{
|
||||
("Schnell (0.5s)", 500),
|
||||
("Mittel (1s)", 1000),
|
||||
("Langsam (2s)", 2000),
|
||||
("Sehr langsam (4s)", 4000),
|
||||
};
|
||||
|
||||
public ActionDialog(DeviceAction action, Color ledColor,
|
||||
LedAnimType ledAnim = LedAnimType.ColorCycle,
|
||||
ushort ledPeriod = 4000,
|
||||
MacroTable? macros = null,
|
||||
int macroSlot = 0,
|
||||
bool showColor = true)
|
||||
{
|
||||
ResultAction = action.Clone();
|
||||
ResultColor = ledColor;
|
||||
ResultAnim = ledAnim;
|
||||
ResultPeriod = ledPeriod;
|
||||
_color = ledColor;
|
||||
_macros = macros ?? new MacroTable();
|
||||
_macroSlot = macroSlot;
|
||||
_showColor = showColor;
|
||||
|
||||
// Bestehende Makro-Steps aus der Tabelle laden
|
||||
for (int i = 0; i < 4; i++)
|
||||
_stepKeycodes[i] = _macros.Steps[_macroSlot, i].Keycode;
|
||||
|
||||
// Formular-Grundeinstellungen
|
||||
Text = "Aktion bearbeiten";
|
||||
@@ -156,20 +236,20 @@ public class ActionDialog : Form
|
||||
_typeCombo = new ComboBox
|
||||
{
|
||||
Location = new Point(80, 12),
|
||||
Width = 208,
|
||||
Width = 316,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
};
|
||||
_typeCombo.Items.AddRange(new object[]
|
||||
{ "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command" });
|
||||
{ "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command", "Makro" });
|
||||
_typeCombo.SelectedIndex = (int)action.Type;
|
||||
_typeCombo.SelectedIndexChanged += OnTypeChanged;
|
||||
|
||||
// ── HID-Tastatur-Panel ────────────────────────────────────────────────
|
||||
_hidKeyPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 110) };
|
||||
_hidKeyPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 110) };
|
||||
|
||||
_captureBtn = new Button
|
||||
{
|
||||
Size = new Size(280, 48),
|
||||
Size = new Size(396, 48),
|
||||
Location = new Point(0, 0),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
Font = new Font(Font.FontFamily, 12, FontStyle.Regular),
|
||||
@@ -213,11 +293,11 @@ public class ActionDialog : Form
|
||||
RefreshCaptureBtn(); // erst nach Checkbox-Initialisierung aufrufen
|
||||
|
||||
// ── Consumer-Panel ────────────────────────────────────────────────────
|
||||
_consumerPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 30) };
|
||||
_consumerPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) };
|
||||
_consumerCombo = new ComboBox
|
||||
{
|
||||
Location = new Point(0, 0),
|
||||
Width = 280,
|
||||
Width = 396,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
};
|
||||
foreach (var (_, name) in s_consumer)
|
||||
@@ -230,12 +310,12 @@ public class ActionDialog : Form
|
||||
_consumerPanel.Controls.Add(_consumerCombo);
|
||||
|
||||
// ── Host-Command-Panel ────────────────────────────────────────────────
|
||||
_cmdPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 30) };
|
||||
_cmdPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) };
|
||||
var cmdLabel = new Label { Text = "Command-ID:", Location = new Point(0, 8), AutoSize = true };
|
||||
_cmdBox = new TextBox
|
||||
{
|
||||
Location = new Point(90, 4),
|
||||
Width = 190,
|
||||
Width = 306,
|
||||
Text = action.Type == ActionType.HostCommand ? $"{action.Data}" : "0",
|
||||
};
|
||||
_cmdPanel.Controls.AddRange(new Control[] { cmdLabel, _cmdBox });
|
||||
@@ -244,14 +324,14 @@ public class ActionDialog : Form
|
||||
_colorPanel = new Panel
|
||||
{
|
||||
Location = new Point(12, 168),
|
||||
Size = new Size(280, 30),
|
||||
Size = new Size(396, 30),
|
||||
Visible = showColor,
|
||||
};
|
||||
var colorLabel = new Label { Text = "LED-Farbe:", Location = new Point(0, 8), AutoSize = true };
|
||||
_colorBtn = new Button
|
||||
{
|
||||
Location = new Point(80, 2),
|
||||
Size = new Size(200, 26),
|
||||
Size = new Size(316, 26),
|
||||
BackColor = ledColor,
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
Text = string.Empty,
|
||||
@@ -260,37 +340,126 @@ public class ActionDialog : Form
|
||||
_colorBtn.Click += OnColorClick;
|
||||
_colorPanel.Controls.AddRange(new Control[] { colorLabel, _colorBtn });
|
||||
|
||||
// ── Buttons Fußzeile ──────────────────────────────────────────────────
|
||||
int footerY = showColor ? 208 : 168;
|
||||
var okBtn = new Button
|
||||
// ── Animation-Panel ───────────────────────────────────────────────────
|
||||
_animPanel = new Panel
|
||||
{
|
||||
Location = new Point(12, 208),
|
||||
Size = new Size(396, 58),
|
||||
Visible = showColor,
|
||||
};
|
||||
|
||||
var animLabel = new Label { Text = "Animation:", Location = new Point(0, 8), AutoSize = true };
|
||||
_animCombo = new ComboBox
|
||||
{
|
||||
Location = new Point(80, 4),
|
||||
Width = 316,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
};
|
||||
foreach (var (name, _, _) in s_anims)
|
||||
_animCombo.Items.Add(name);
|
||||
|
||||
int animIdx = Array.FindIndex(s_anims, a => a.Type == ledAnim);
|
||||
_animCombo.SelectedIndex = animIdx >= 0 ? animIdx : 0;
|
||||
_animCombo.SelectedIndexChanged += OnAnimChanged;
|
||||
|
||||
var periodLabel = new Label { Text = "Tempo:", Location = new Point(0, 36), AutoSize = true };
|
||||
_periodCombo = new ComboBox
|
||||
{
|
||||
Location = new Point(80, 32),
|
||||
Width = 316,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
};
|
||||
foreach (var (name, _) in s_periods)
|
||||
_periodCombo.Items.Add(name);
|
||||
|
||||
int periodIdx = Array.FindIndex(s_periods, p => p.Ms == ledPeriod);
|
||||
_periodCombo.SelectedIndex = periodIdx >= 0 ? periodIdx : 3; // Default: sehr langsam
|
||||
|
||||
_animPanel.Controls.AddRange(new Control[]
|
||||
{ animLabel, _animCombo, periodLabel, _periodCombo });
|
||||
|
||||
// ── Makro-Panel (4 Steps) ─────────────────────────────────────────────
|
||||
_macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 4 * 30 + 22) };
|
||||
|
||||
var macroHint = new Label
|
||||
{
|
||||
Text = "Klicke einen Step, dann Taste drücken. Leere Steps werden übersprungen.",
|
||||
Location = new Point(0, 0),
|
||||
Size = new Size(396, 28),
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Font = new Font(Font.FontFamily, 8),
|
||||
};
|
||||
_macroPanel.Controls.Add(macroHint);
|
||||
|
||||
for (int step = 0; step < 4; step++)
|
||||
{
|
||||
int s = step;
|
||||
int y2 = 30 + step * 30;
|
||||
|
||||
var stepLabel = new Label
|
||||
{
|
||||
Text = $"Step {step + 1}:",
|
||||
Location = new Point(0, y2 + 6),
|
||||
AutoSize = true,
|
||||
};
|
||||
|
||||
_stepBtns[step] = new Button
|
||||
{
|
||||
Location = new Point(50, y2),
|
||||
Size = new Size(160, 24),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
Cursor = Cursors.Hand,
|
||||
};
|
||||
_stepBtns[step].FlatAppearance.BorderColor = Color.DarkGray;
|
||||
_stepBtns[step].Click += (_, _) => StartMacroCapture(s);
|
||||
|
||||
_stepCtrl[step] = new CheckBox { Text = "Strg", Location = new Point(218, y2 + 4), AutoSize = true };
|
||||
_stepShift[step] = new CheckBox { Text = "Shift", Location = new Point(268, y2 + 4), AutoSize = true };
|
||||
_stepAlt[step] = new CheckBox { Text = "Alt", Location = new Point(326, y2 + 4), AutoSize = true };
|
||||
|
||||
// Bestehende Werte aus Makro-Tabelle laden
|
||||
_stepCtrl[step].Checked = (_macros.Steps[_macroSlot, step].Modifier & 0x01) != 0;
|
||||
_stepShift[step].Checked = (_macros.Steps[_macroSlot, step].Modifier & 0x02) != 0;
|
||||
_stepAlt[step].Checked = (_macros.Steps[_macroSlot, step].Modifier & 0x04) != 0;
|
||||
|
||||
_stepCtrl[step].CheckedChanged += (_, _) => RefreshStepBtn(s);
|
||||
_stepShift[step].CheckedChanged += (_, _) => RefreshStepBtn(s);
|
||||
_stepAlt[step].CheckedChanged += (_, _) => RefreshStepBtn(s);
|
||||
|
||||
_macroPanel.Controls.AddRange(new Control[]
|
||||
{ stepLabel, _stepBtns[step], _stepCtrl[step], _stepShift[step], _stepAlt[step] });
|
||||
|
||||
RefreshStepBtn(step);
|
||||
}
|
||||
|
||||
// ── Buttons Fußzeile ─────────────────────────────────────────────────
|
||||
_okBtn = new Button
|
||||
{
|
||||
Text = "OK",
|
||||
DialogResult = DialogResult.OK,
|
||||
Location = new Point(112, footerY),
|
||||
Width = 80,
|
||||
};
|
||||
okBtn.Click += OnOk;
|
||||
_okBtn.Click += OnOk;
|
||||
|
||||
var cancelBtn = new Button
|
||||
_cancelBtn = new Button
|
||||
{
|
||||
Text = "Abbrechen",
|
||||
DialogResult = DialogResult.Cancel,
|
||||
Location = new Point(200, footerY),
|
||||
Width = 92,
|
||||
};
|
||||
|
||||
AcceptButton = okBtn;
|
||||
CancelButton = cancelBtn;
|
||||
ClientSize = new Size(304, footerY + 44);
|
||||
AcceptButton = _okBtn;
|
||||
CancelButton = _cancelBtn;
|
||||
|
||||
Controls.AddRange(new Control[]
|
||||
{
|
||||
typeLabel, _typeCombo,
|
||||
_hidKeyPanel, _consumerPanel, _cmdPanel, _colorPanel,
|
||||
okBtn, cancelBtn,
|
||||
_hidKeyPanel, _consumerPanel, _cmdPanel, _macroPanel,
|
||||
_colorPanel, _animPanel,
|
||||
_okBtn, _cancelBtn,
|
||||
});
|
||||
|
||||
UpdatePanelVisibility();
|
||||
UpdatePanelVisibility(); // setzt Visibility + ruft UpdateLayout auf
|
||||
}
|
||||
|
||||
// ── Panel-Sichtbarkeit ────────────────────────────────────────────────────
|
||||
@@ -301,12 +470,62 @@ public class ActionDialog : Form
|
||||
UpdatePanelVisibility();
|
||||
}
|
||||
|
||||
private void OnAnimChanged(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateAnimVisibility();
|
||||
}
|
||||
|
||||
private void UpdatePanelVisibility()
|
||||
{
|
||||
var t = (ActionType)_typeCombo.SelectedIndex;
|
||||
_hidKeyPanel.Visible = t == ActionType.HidKey;
|
||||
_consumerPanel.Visible = t == ActionType.HidConsumer;
|
||||
_cmdPanel.Visible = t == ActionType.HostCommand;
|
||||
_macroPanel.Visible = t == ActionType.Macro;
|
||||
_colorPanel.Visible = _showColor;
|
||||
_animPanel.Visible = _showColor;
|
||||
UpdateAnimVisibility();
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
// Positioniert LED-Panels dynamisch unterhalb des aktiven Typ-Panels
|
||||
// und passt die Formulargröße an.
|
||||
private void UpdateLayout()
|
||||
{
|
||||
var t = (ActionType)_typeCombo.SelectedIndex;
|
||||
|
||||
int contentH = t switch
|
||||
{
|
||||
ActionType.HidKey => 110,
|
||||
ActionType.HidConsumer => 30,
|
||||
ActionType.HostCommand => 30,
|
||||
ActionType.Macro => 4 * 30 + 22,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
int ledY = 48 + contentH + 10;
|
||||
int footerY = ledY;
|
||||
|
||||
if (_showColor)
|
||||
{
|
||||
_colorPanel.Location = new Point(12, ledY);
|
||||
_animPanel.Location = new Point(12, ledY + 38);
|
||||
footerY = ledY + 38 + 62;
|
||||
}
|
||||
|
||||
_okBtn.Location = new Point(224, footerY);
|
||||
_cancelBtn.Location = new Point(312, footerY);
|
||||
ClientSize = new Size(420, footerY + 44);
|
||||
}
|
||||
|
||||
private void UpdateAnimVisibility()
|
||||
{
|
||||
if (_animPanel.Visible && _animCombo.SelectedIndex >= 0)
|
||||
{
|
||||
var (_, animType, showColorFlag) = s_anims[_animCombo.SelectedIndex];
|
||||
_colorBtn.Enabled = showColorFlag;
|
||||
_periodCombo.Enabled = animType != LedAnimType.Static;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tasten-Erfassung ──────────────────────────────────────────────────────
|
||||
@@ -317,6 +536,40 @@ public class ActionDialog : Form
|
||||
else StartCapture();
|
||||
}
|
||||
|
||||
// ── Makro-Step-Capture ────────────────────────────────────────────────────
|
||||
|
||||
private void StartMacroCapture(int step)
|
||||
{
|
||||
_captureStep = step;
|
||||
_stepBtns[step].Text = "Taste drücken...";
|
||||
_stepBtns[step].BackColor = Color.FromArgb(255, 220, 80);
|
||||
_stepBtns[step].ForeColor = Color.Black;
|
||||
}
|
||||
|
||||
private void RefreshStepBtn(int step)
|
||||
{
|
||||
if (_captureStep == step) return; // gerade im Capture-Modus
|
||||
byte kc = _stepKeycodes[step];
|
||||
if (kc == 0)
|
||||
{
|
||||
_stepBtns[step].Text = "— (leer)";
|
||||
_stepBtns[step].BackColor = SystemColors.Control;
|
||||
_stepBtns[step].ForeColor = SystemColors.GrayText;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = HidKeyName(kc);
|
||||
var parts = new List<string>();
|
||||
if (_stepCtrl[step].Checked) parts.Add("Strg");
|
||||
if (_stepShift[step].Checked) parts.Add("Shift");
|
||||
if (_stepAlt[step].Checked) parts.Add("Alt");
|
||||
parts.Add(name);
|
||||
_stepBtns[step].Text = string.Join("+", parts);
|
||||
_stepBtns[step].BackColor = SystemColors.Control;
|
||||
_stepBtns[step].ForeColor = SystemColors.ControlText;
|
||||
}
|
||||
}
|
||||
|
||||
private void StartCapture()
|
||||
{
|
||||
_capturing = true;
|
||||
@@ -334,14 +587,14 @@ public class ActionDialog : Form
|
||||
|
||||
private void RefreshCaptureBtn()
|
||||
{
|
||||
if (_hidKeycode != 0 && s_hidNames.TryGetValue(_hidKeycode, out string? kn))
|
||||
if (_hidKeycode != 0)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (_chkCtrl.Checked) parts.Add("Strg");
|
||||
if (_chkShift.Checked) parts.Add("Shift");
|
||||
if (_chkAlt.Checked) parts.Add("Alt");
|
||||
if (_chkWin.Checked) parts.Add("Win");
|
||||
parts.Add(kn);
|
||||
parts.Add(HidKeyName(_hidKeycode));
|
||||
_captureBtn.Text = string.Join(" + ", parts);
|
||||
}
|
||||
else
|
||||
@@ -353,41 +606,65 @@ public class ActionDialog : Form
|
||||
_captureBtn.FlatAppearance.BorderColor = Color.DarkGray;
|
||||
}
|
||||
|
||||
// Tasten werden form-weit abgefangen (KeyPreview = true)
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
// ProcessCmdKey wird vor jeder Dialog-Verarbeitung aufgerufen (Enter, Pfeiltasten,
|
||||
// Tab usw. werden normalerweise als "Dialog-Tasten" verschluckt, bevor OnKeyDown
|
||||
// feuert). Daher hier die Capture-Logik – so kommen auch ↑↓←→ und Enter an.
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
if (!_capturing) { base.OnKeyDown(e); return; }
|
||||
const int WM_KEYDOWN = 0x0100;
|
||||
const int WM_SYSKEYDOWN = 0x0104;
|
||||
if (msg.Msg != WM_KEYDOWN && msg.Msg != WM_SYSKEYDOWN)
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
|
||||
if (!_capturing && _captureStep < 0)
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
|
||||
Keys vk = keyData & Keys.KeyCode;
|
||||
bool ctrl = (keyData & Keys.Control) != 0;
|
||||
bool shift = (keyData & Keys.Shift) != 0;
|
||||
bool alt = (keyData & Keys.Alt) != 0;
|
||||
|
||||
// Reine Modifier-Tasten ignorieren – auf echte Taste warten
|
||||
if (e.KeyCode is Keys.ControlKey or Keys.LControlKey or Keys.RControlKey
|
||||
or Keys.ShiftKey or Keys.LShiftKey or Keys.RShiftKey
|
||||
or Keys.Menu or Keys.LMenu or Keys.RMenu
|
||||
or Keys.LWin or Keys.RWin or Keys.Apps)
|
||||
if (vk is Keys.ControlKey or Keys.LControlKey or Keys.RControlKey
|
||||
or Keys.ShiftKey or Keys.LShiftKey or Keys.RShiftKey
|
||||
or Keys.Menu or Keys.LMenu or Keys.RMenu
|
||||
or Keys.LWin or Keys.RWin or Keys.Apps)
|
||||
return true; // schlucken, aber Capture läuft weiter
|
||||
|
||||
// ── Makro-Step-Capture ────────────────────────────────────────────────
|
||||
if (_captureStep >= 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
int s = _captureStep;
|
||||
_captureStep = -1;
|
||||
if (vk == Keys.Escape)
|
||||
{
|
||||
_stepKeycodes[s] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stepKeycodes[s] = VkToHid(vk);
|
||||
_stepCtrl[s].Checked = ctrl;
|
||||
_stepShift[s].Checked = shift;
|
||||
_stepAlt[s].Checked = alt;
|
||||
}
|
||||
RefreshStepBtn(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Escape bricht Erfassung ab (ohne Wert zu ändern)
|
||||
if (e.KeyCode == Keys.Escape)
|
||||
// ── HID-Tastatur-Capture ──────────────────────────────────────────────
|
||||
if (vk == Keys.Escape)
|
||||
{
|
||||
StopCapture();
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Modifikatoren übernehmen
|
||||
_chkCtrl.Checked = e.Control;
|
||||
_chkShift.Checked = e.Shift;
|
||||
_chkAlt.Checked = e.Alt;
|
||||
|
||||
// VK → HID-Code
|
||||
_hidKeycode = s_vkToHid.TryGetValue(e.KeyCode, out byte hid) ? hid : (byte)0;
|
||||
|
||||
StopCapture();
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
else
|
||||
{
|
||||
_chkCtrl.Checked = ctrl;
|
||||
_chkShift.Checked = shift;
|
||||
_chkAlt.Checked = alt;
|
||||
_hidKeycode = VkToHid(vk);
|
||||
StopCapture();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Farbe ─────────────────────────────────────────────────────────────────
|
||||
@@ -435,7 +712,30 @@ public class ActionDialog : Form
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == ActionType.Macro)
|
||||
{
|
||||
// Steps in die Makro-Tabelle schreiben
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
byte mod = 0;
|
||||
if (_stepCtrl[i].Checked) mod |= 0x01;
|
||||
if (_stepShift[i].Checked) mod |= 0x02;
|
||||
if (_stepAlt[i].Checked) mod |= 0x04;
|
||||
_macros.Steps[_macroSlot, i].Keycode = _stepKeycodes[i];
|
||||
_macros.Steps[_macroSlot, i].Modifier = mod;
|
||||
}
|
||||
data = (ushort)_macroSlot;
|
||||
}
|
||||
|
||||
ResultAction = new DeviceAction { Type = type, Data = data };
|
||||
ResultColor = _color;
|
||||
|
||||
if (_animPanel.Visible && _animCombo.SelectedIndex >= 0)
|
||||
{
|
||||
ResultAnim = s_anims[_animCombo.SelectedIndex].Type;
|
||||
ResultPeriod = _periodCombo.SelectedIndex >= 0
|
||||
? s_periods[_periodCombo.SelectedIndex].Ms
|
||||
: (ushort)4000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user