Initial commit

This commit is contained in:
2026-03-29 14:42:20 +02:00
commit 747bec985d
10 changed files with 1618 additions and 0 deletions
+441
View File
@@ -0,0 +1,441 @@
// 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)
// Keine Aktion → nichts
//
// Optional (nur MX-Buttons): LED-Basisfarbe
namespace VersaGUI;
public class ActionDialog : Form
{
// ── Ergebnis ──────────────────────────────────────────────────────────────
public DeviceAction ResultAction { get; private set; }
public Color ResultColor { 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;
// LED-Farbe
private readonly Panel _colorPanel;
private readonly Button _colorBtn;
private Color _color;
// Capture-Zustand
private bool _capturing;
private byte _hidKeycode; // 0 = nicht belegt
// ── Lookup-Tabellen ───────────────────────────────────────────────────────
// Windows VK → HID Keyboard Usage (USB HID Usage Table 0x07)
private static readonly Dictionary<Keys, byte> s_vkToHid = 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,
};
// HID-Code → lesbarer Name (für Capture-Button-Beschriftung)
private static readonly Dictionary<byte, string> s_hidNames = 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.",
};
// 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 ───────────────────────────────────────────────────────────
public ActionDialog(DeviceAction action, Color ledColor, bool showColor = true)
{
ResultAction = action.Clone();
ResultColor = ledColor;
_color = ledColor;
// 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 = 208,
DropDownStyle = ComboBoxStyle.DropDownList,
};
_typeCombo.Items.AddRange(new object[]
{ "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command" });
_typeCombo.SelectedIndex = (int)action.Type;
_typeCombo.SelectedIndexChanged += OnTypeChanged;
// ── HID-Tastatur-Panel ────────────────────────────────────────────────
_hidKeyPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 110) };
_captureBtn = new Button
{
Size = new Size(280, 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(280, 30) };
_consumerCombo = new ComboBox
{
Location = new Point(0, 0),
Width = 280,
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(280, 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,
Text = action.Type == ActionType.HostCommand ? $"{action.Data}" : "0",
};
_cmdPanel.Controls.AddRange(new Control[] { cmdLabel, _cmdBox });
// ── Farb-Panel ────────────────────────────────────────────────────────
_colorPanel = new Panel
{
Location = new Point(12, 168),
Size = new Size(280, 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),
BackColor = ledColor,
FlatStyle = FlatStyle.Flat,
Text = string.Empty,
};
_colorBtn.FlatAppearance.BorderSize = 1;
_colorBtn.Click += OnColorClick;
_colorPanel.Controls.AddRange(new Control[] { colorLabel, _colorBtn });
// ── Buttons Fußzeile ──────────────────────────────────────────────────
int footerY = showColor ? 208 : 168;
var okBtn = new Button
{
Text = "OK",
DialogResult = DialogResult.OK,
Location = new Point(112, footerY),
Width = 80,
};
okBtn.Click += OnOk;
var 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);
Controls.AddRange(new Control[]
{
typeLabel, _typeCombo,
_hidKeyPanel, _consumerPanel, _cmdPanel, _colorPanel,
okBtn, cancelBtn,
});
UpdatePanelVisibility();
}
// ── Panel-Sichtbarkeit ────────────────────────────────────────────────────
private void OnTypeChanged(object? sender, EventArgs e)
{
StopCapture();
UpdatePanelVisibility();
}
private void UpdatePanelVisibility()
{
var t = (ActionType)_typeCombo.SelectedIndex;
_hidKeyPanel.Visible = t == ActionType.HidKey;
_consumerPanel.Visible = t == ActionType.HidConsumer;
_cmdPanel.Visible = t == ActionType.HostCommand;
}
// ── Tasten-Erfassung ──────────────────────────────────────────────────────
private void OnCaptureClick(object? sender, EventArgs e)
{
if (_capturing) StopCapture();
else StartCapture();
}
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 && s_hidNames.TryGetValue(_hidKeycode, out string? kn))
{
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);
_captureBtn.Text = string.Join(" + ", parts);
}
else
{
_captureBtn.Text = "— (klicken zum Erfassen)";
}
_captureBtn.BackColor = SystemColors.Control;
_captureBtn.ForeColor = SystemColors.ControlText;
_captureBtn.FlatAppearance.BorderColor = Color.DarkGray;
}
// Tasten werden form-weit abgefangen (KeyPreview = true)
protected override void OnKeyDown(KeyEventArgs e)
{
if (!_capturing) { base.OnKeyDown(e); return; }
// 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)
{
e.Handled = true;
return;
}
// Escape bricht Erfassung ab (ohne Wert zu ändern)
if (e.KeyCode == 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;
}
// ── 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 (065535 erwartet).",
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
DialogResult = DialogResult.None;
return;
}
break;
}
ResultAction = new DeviceAction { Type = type, Data = data };
ResultColor = _color;
}
}