775 lines
33 KiB
C#
775 lines
33 KiB
C#
// ActionDialog.cs
|
||
// Modaler Dialog zum Bearbeiten einer einzelnen Button-/Encoder-Aktion.
|
||
//
|
||
// Layout je nach Typ:
|
||
// HID Tastatur → Capture-Button (Klicken + Taste drücken) + Modifier-Checkboxen
|
||
// HID Consumer → Dropdown mit benannten Medien-/Lautstärke-Aktionen
|
||
// Host Command → Zahlen-Eingabe (Command-ID)
|
||
// Makro → acht HID-Key-Steps
|
||
// Profilwechsel → Zielprofil oder zyklisch nächstes Profil
|
||
// Keine Aktion → nichts
|
||
//
|
||
// Optional (nur MX-Buttons): LED-Basisfarbe + vier auswählbare Animationen +
|
||
// Geschwindigkeit. Das Binärmodell akzeptiert zusätzlich drei Animationen.
|
||
|
||
namespace VersaGUI;
|
||
|
||
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;
|
||
|
||
// HID Tastatur
|
||
private readonly Panel _hidKeyPanel;
|
||
private readonly Button _captureBtn;
|
||
private readonly CheckBox _chkCtrl, _chkShift, _chkAlt, _chkWin;
|
||
|
||
// HID Consumer
|
||
private readonly Panel _consumerPanel;
|
||
private readonly ComboBox _consumerCombo;
|
||
|
||
// Host Command
|
||
private readonly Panel _cmdPanel;
|
||
private readonly TextBox _cmdBox;
|
||
|
||
// Profil wechseln
|
||
private readonly Panel _profilePanel;
|
||
private readonly ComboBox _profileCombo;
|
||
|
||
// LED-Farbe
|
||
private readonly Panel _colorPanel;
|
||
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[8];
|
||
private readonly CheckBox[] _stepCtrl = new CheckBox[8];
|
||
private readonly CheckBox[] _stepShift = new CheckBox[8];
|
||
private readonly CheckBox[] _stepAlt = new CheckBox[8];
|
||
private readonly byte[] _stepKeycodes = new byte[8];
|
||
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 ───────────────────────────────────────────────────────
|
||
|
||
// 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()
|
||
{
|
||
[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 → 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()
|
||
{
|
||
[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 =
|
||
{
|
||
(0x00CD, "Play / Pause"),
|
||
(0x00B5, "Nächster Titel"),
|
||
(0x00B6, "Vorheriger Titel"),
|
||
(0x00B7, "Stop"),
|
||
(0x00E9, "Lauter"),
|
||
(0x00EA, "Leiser"),
|
||
(0x00E2, "Stummschalten"),
|
||
(0x0192, "Taschenrechner"),
|
||
(0x0223, "Browser – Startseite"),
|
||
(0x0224, "Browser – Zurück"),
|
||
(0x0225, "Browser – Vor"),
|
||
(0x00B0, "Aufnahme"),
|
||
};
|
||
|
||
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||
|
||
// 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 < 8; i++)
|
||
_stepKeycodes[i] = _macros.Steps[_macroSlot, i].Keycode;
|
||
|
||
// Formular-Grundeinstellungen
|
||
Text = "Aktion bearbeiten";
|
||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||
StartPosition = FormStartPosition.CenterParent;
|
||
MinimizeBox = false;
|
||
MaximizeBox = false;
|
||
KeyPreview = true; // Tastendrücke abfangen bevor Controls sie kriegen
|
||
|
||
// ── Typ-Auswahl ───────────────────────────────────────────────────────
|
||
var typeLabel = new Label { Text = "Typ:", Location = new Point(12, 16), AutoSize = true };
|
||
_typeCombo = new ComboBox
|
||
{
|
||
Location = new Point(80, 12),
|
||
Width = 316,
|
||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||
};
|
||
_typeCombo.Items.AddRange(new object[]
|
||
{ "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command", "Makro", "Profil wechseln" });
|
||
_typeCombo.SelectedIndex = (int)action.Type;
|
||
_typeCombo.SelectedIndexChanged += OnTypeChanged;
|
||
|
||
// ── HID-Tastatur-Panel ────────────────────────────────────────────────
|
||
_hidKeyPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 110) };
|
||
|
||
_captureBtn = new Button
|
||
{
|
||
Size = new Size(396, 48),
|
||
Location = new Point(0, 0),
|
||
FlatStyle = FlatStyle.Flat,
|
||
Font = new Font(Font.FontFamily, 12, FontStyle.Regular),
|
||
Cursor = Cursors.Hand,
|
||
};
|
||
_captureBtn.FlatAppearance.BorderColor = Color.DarkGray;
|
||
_captureBtn.Click += OnCaptureClick;
|
||
|
||
var hint = new Label
|
||
{
|
||
Text = "Klicken und dann Taste drücken | Esc = Abbrechen",
|
||
Location = new Point(0, 54),
|
||
AutoSize = true,
|
||
ForeColor = SystemColors.GrayText,
|
||
Font = new Font(Font.FontFamily, 8),
|
||
};
|
||
|
||
_chkCtrl = new CheckBox { Text = "Strg", Location = new Point(0, 78), AutoSize = true };
|
||
_chkShift = new CheckBox { Text = "Shift", Location = new Point(60, 78), AutoSize = true };
|
||
_chkAlt = new CheckBox { Text = "Alt", Location = new Point(120, 78), AutoSize = true };
|
||
_chkWin = new CheckBox { Text = "Win", Location = new Point(176, 78), AutoSize = true };
|
||
_chkCtrl.CheckedChanged += (_, _) => RefreshCaptureBtn();
|
||
_chkShift.CheckedChanged += (_, _) => RefreshCaptureBtn();
|
||
_chkAlt.CheckedChanged += (_, _) => RefreshCaptureBtn();
|
||
_chkWin.CheckedChanged += (_, _) => RefreshCaptureBtn();
|
||
|
||
// Aus bestehender Aktion befüllen
|
||
if (action.Type == ActionType.HidKey)
|
||
{
|
||
_hidKeycode = (byte)(action.Data & 0xFF);
|
||
byte mod = (byte)(action.Data >> 8);
|
||
_chkCtrl.Checked = (mod & 0x11) != 0;
|
||
_chkShift.Checked = (mod & 0x22) != 0;
|
||
_chkAlt.Checked = (mod & 0x44) != 0;
|
||
_chkWin.Checked = (mod & 0x88) != 0;
|
||
}
|
||
|
||
_hidKeyPanel.Controls.AddRange(new Control[]
|
||
{ _captureBtn, hint, _chkCtrl, _chkShift, _chkAlt, _chkWin });
|
||
|
||
RefreshCaptureBtn(); // erst nach Checkbox-Initialisierung aufrufen
|
||
|
||
// ── Consumer-Panel ────────────────────────────────────────────────────
|
||
_consumerPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) };
|
||
_consumerCombo = new ComboBox
|
||
{
|
||
Location = new Point(0, 0),
|
||
Width = 396,
|
||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||
};
|
||
foreach (var (_, name) in s_consumer)
|
||
_consumerCombo.Items.Add(name);
|
||
|
||
int consumerIdx = action.Type == ActionType.HidConsumer
|
||
? Array.FindIndex(s_consumer, e => e.Usage == action.Data)
|
||
: -1;
|
||
_consumerCombo.SelectedIndex = consumerIdx >= 0 ? consumerIdx : 0;
|
||
_consumerPanel.Controls.Add(_consumerCombo);
|
||
|
||
// ── Host-Command-Panel ────────────────────────────────────────────────
|
||
_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 = 306,
|
||
Text = action.Type == ActionType.HostCommand ? $"{action.Data}" : "0",
|
||
};
|
||
_cmdPanel.Controls.AddRange(new Control[] { cmdLabel, _cmdBox });
|
||
|
||
// ── Profil-Panel ──────────────────────────────────────────────────────
|
||
_profilePanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) };
|
||
var profileLabel = new Label { Text = "Ziel-Profil:", Location = new Point(0, 8), AutoSize = true };
|
||
_profileCombo = new ComboBox
|
||
{
|
||
Location = new Point(90, 4),
|
||
Width = 200,
|
||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||
};
|
||
_profileCombo.Items.AddRange(new object[]
|
||
{ "Nächstes Profil (Zyklus)", "Profil 1", "Profil 2", "Profil 3" });
|
||
_profileCombo.SelectedIndex = action.Type == ActionType.ProfileSwitch
|
||
? (action.Data is 0x00FF or 0xFFFF
|
||
? 0
|
||
: Math.Clamp((int)action.Data + 1, 1, 3))
|
||
: 0;
|
||
_profilePanel.Controls.AddRange(new Control[] { profileLabel, _profileCombo });
|
||
|
||
// ── Farb-Panel ────────────────────────────────────────────────────────
|
||
_colorPanel = new Panel
|
||
{
|
||
Location = new Point(12, 168),
|
||
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(316, 26),
|
||
BackColor = ledColor,
|
||
FlatStyle = FlatStyle.Flat,
|
||
Text = string.Empty,
|
||
};
|
||
_colorBtn.FlatAppearance.BorderSize = 1;
|
||
_colorBtn.Click += OnColorClick;
|
||
_colorPanel.Controls.AddRange(new Control[] { colorLabel, _colorBtn });
|
||
|
||
// ── 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 (8 Steps) ─────────────────────────────────────────────
|
||
_macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 8 * 30 + 22) };
|
||
|
||
var macroHint = new Label
|
||
{
|
||
Text = "Klicke einen Step, dann Taste drücken. Der erste leere Step beendet das Makro.",
|
||
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 < 8; 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,
|
||
Width = 80,
|
||
};
|
||
_okBtn.Click += OnOk;
|
||
|
||
_cancelBtn = new Button
|
||
{
|
||
Text = "Abbrechen",
|
||
DialogResult = DialogResult.Cancel,
|
||
Width = 92,
|
||
};
|
||
|
||
AcceptButton = _okBtn;
|
||
CancelButton = _cancelBtn;
|
||
|
||
Controls.AddRange(new Control[]
|
||
{
|
||
typeLabel, _typeCombo,
|
||
_hidKeyPanel, _consumerPanel, _cmdPanel, _profilePanel, _macroPanel,
|
||
_colorPanel, _animPanel,
|
||
_okBtn, _cancelBtn,
|
||
});
|
||
|
||
UpdatePanelVisibility(); // setzt Visibility + ruft UpdateLayout auf
|
||
}
|
||
|
||
// ── Panel-Sichtbarkeit ────────────────────────────────────────────────────
|
||
|
||
private void OnTypeChanged(object? sender, EventArgs e)
|
||
{
|
||
StopCapture();
|
||
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;
|
||
_profilePanel.Visible = t == ActionType.ProfileSwitch;
|
||
_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.ProfileSwitch => 30,
|
||
ActionType.Macro => 8 * 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 ──────────────────────────────────────────────────────
|
||
|
||
private void OnCaptureClick(object? sender, EventArgs e)
|
||
{
|
||
if (_capturing) StopCapture();
|
||
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;
|
||
_captureBtn.Text = "Taste drücken...";
|
||
_captureBtn.BackColor = Color.FromArgb(255, 220, 80);
|
||
_captureBtn.ForeColor = Color.Black;
|
||
_captureBtn.FlatAppearance.BorderColor = Color.DarkOrange;
|
||
}
|
||
|
||
private void StopCapture()
|
||
{
|
||
_capturing = false;
|
||
RefreshCaptureBtn();
|
||
}
|
||
|
||
private void RefreshCaptureBtn()
|
||
{
|
||
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(HidKeyName(_hidKeycode));
|
||
_captureBtn.Text = string.Join(" + ", parts);
|
||
}
|
||
else
|
||
{
|
||
_captureBtn.Text = "— (klicken zum Erfassen)";
|
||
}
|
||
_captureBtn.BackColor = SystemColors.Control;
|
||
_captureBtn.ForeColor = SystemColors.ControlText;
|
||
_captureBtn.FlatAppearance.BorderColor = Color.DarkGray;
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
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 (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)
|
||
{
|
||
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;
|
||
}
|
||
|
||
// ── HID-Tastatur-Capture ──────────────────────────────────────────────
|
||
if (vk == Keys.Escape)
|
||
{
|
||
StopCapture();
|
||
}
|
||
else
|
||
{
|
||
_chkCtrl.Checked = ctrl;
|
||
_chkShift.Checked = shift;
|
||
_chkAlt.Checked = alt;
|
||
_hidKeycode = VkToHid(vk);
|
||
StopCapture();
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ── Farbe ─────────────────────────────────────────────────────────────────
|
||
|
||
private void OnColorClick(object? sender, EventArgs e)
|
||
{
|
||
using var dlg = new ColorDialog { Color = _color, FullOpen = true };
|
||
if (dlg.ShowDialog(this) == DialogResult.OK)
|
||
{
|
||
_color = dlg.Color;
|
||
_colorBtn.BackColor = dlg.Color;
|
||
}
|
||
}
|
||
|
||
// ── OK ────────────────────────────────────────────────────────────────────
|
||
|
||
private void OnOk(object? sender, EventArgs e)
|
||
{
|
||
var type = (ActionType)_typeCombo.SelectedIndex;
|
||
ushort data = 0;
|
||
|
||
switch (type)
|
||
{
|
||
case ActionType.HidKey:
|
||
byte mod = 0;
|
||
if (_chkCtrl.Checked) mod |= 0x01; // LCtrl
|
||
if (_chkShift.Checked) mod |= 0x02; // LShift
|
||
if (_chkAlt.Checked) mod |= 0x04; // LAlt
|
||
if (_chkWin.Checked) mod |= 0x08; // LGUI
|
||
data = (ushort)(_hidKeycode | (mod << 8));
|
||
break;
|
||
|
||
case ActionType.HidConsumer:
|
||
data = s_consumer[_consumerCombo.SelectedIndex].Usage;
|
||
break;
|
||
|
||
case ActionType.HostCommand:
|
||
if (!ushort.TryParse(_cmdBox.Text.Trim(), out data))
|
||
{
|
||
MessageBox.Show("Ungültige Command-ID (0–65535 erwartet).",
|
||
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
DialogResult = DialogResult.None;
|
||
return;
|
||
}
|
||
break;
|
||
|
||
case ActionType.ProfileSwitch:
|
||
data = _profileCombo.SelectedIndex == 0
|
||
? (ushort)0xFFFF // Zyklus: Firmware rechnet (current+1)%3
|
||
: (ushort)(_profileCombo.SelectedIndex - 1); // Profil 0/1/2
|
||
break;
|
||
}
|
||
|
||
if (type == ActionType.Macro)
|
||
{
|
||
// Steps in die Makro-Tabelle schreiben
|
||
for (int i = 0; i < 8; 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;
|
||
}
|
||
}
|
||
}
|