Audio cues: redesign the cue UI - plain cue list + a "(none)" sound option replaces the tickboxes
The per-cue enable tickboxes are gone. The cue list is now a plain list of names; arrowing it previews that cue's current sound (custom or chosen default). The sound list below gains a "(none)" entry at the top: picking it turns the cue off (reusing each cue's existing on/off storage - per-profile cues persist off-ness in the profile, machine-wide cues in global settings), and picking a numbered variant turns the cue on and records that sound. All cues default on, on the first variant; "(none)" is never the default. Part of the larger Preferences overhaul; the 4-tab restructure, the Startup-behaviour/Update moves, and the missing-file error are the remaining steps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
aed555cc2e
commit
0bd3ef0018
@@ -56,9 +56,10 @@ internal sealed class PreferencesDialog : Form
|
||||
Padding = new Padding(0, 6, 0, 4),
|
||||
};
|
||||
|
||||
private readonly CheckedListBox cueList = new()
|
||||
// A plain list of cue names (no tickboxes any more - on/off is the "(none)" entry in the sound
|
||||
// list below). Arrowing it previews the cue's currently-configured sound.
|
||||
private readonly ListBox cueList = new()
|
||||
{
|
||||
CheckOnClick = true,
|
||||
IntegralHeight = false,
|
||||
Height = 130,
|
||||
Width = 360,
|
||||
@@ -376,27 +377,19 @@ internal sealed class PreferencesDialog : Form
|
||||
cueList.Items.Clear();
|
||||
foreach (var c in cueRows)
|
||||
{
|
||||
cueList.Items.Add(c.DisplayName, c.LoadEnabled());
|
||||
cueList.Items.Add(c.DisplayName);
|
||||
}
|
||||
if (cueList.Items.Count > 0) cueList.SelectedIndex = 0;
|
||||
cueList.ItemCheck += (_, e) =>
|
||||
{
|
||||
// ItemCheck fires BEFORE the visual state actually flips; e.NewValue is what
|
||||
// it's about to become, so the persisted value matches what the user just
|
||||
// clicked.
|
||||
if (e.Index < 0 || e.Index >= cueRows.Length) return;
|
||||
var nowEnabled = e.NewValue == CheckState.Checked;
|
||||
var row = cueRows[e.Index];
|
||||
row.SaveEnabled(nowEnabled);
|
||||
// Machine-wide rows (the Startup cue) persist immediately and aren't part of the
|
||||
// profile, so they must not arm the "save profile?" prompt on the way out.
|
||||
if (row.IsProfileSetting) ChangedAnyProfileSetting = true;
|
||||
};
|
||||
|
||||
// Selection changes update the two action buttons' labels so they always tell the
|
||||
// user which cue they're about to act on. Refreshed eagerly at construction time
|
||||
// for the initial selection too.
|
||||
cueList.SelectedIndexChanged += (_, _) => { RefreshCueActionButtons(); RefreshDefaultSoundList(); };
|
||||
// Selection change: refresh the action-button labels and the sound list, and preview the
|
||||
// cue's current sound so arrowing the list lets the user hear each cue. (Enable/disable is
|
||||
// no longer a tickbox here - it's the "(none)" entry in the sound list.)
|
||||
cueList.SelectedIndexChanged += (_, _) =>
|
||||
{
|
||||
RefreshCueActionButtons();
|
||||
RefreshDefaultSoundList();
|
||||
PreviewSelectedCueCurrentSound();
|
||||
};
|
||||
RefreshCueActionButtons();
|
||||
|
||||
playSelectedCueButton.Click += (_, _) =>
|
||||
@@ -734,24 +727,26 @@ internal sealed class PreferencesDialog : Form
|
||||
}
|
||||
var cue = cueRows[idx];
|
||||
var variants = CueSounds.Variants(cue.DefaultFileName);
|
||||
if (variants.Count == 0)
|
||||
{
|
||||
defaultSoundLabel.Text = "Choose default soun&d (Alt+D): (no built-in sounds)";
|
||||
defaultSoundList.Enabled = false;
|
||||
return;
|
||||
}
|
||||
defaultSoundLabel.Text = "Choose default soun&d (Alt+D):";
|
||||
defaultSoundLabel.Text = "Choose soun&d (Alt+D):";
|
||||
defaultSoundList.Enabled = true;
|
||||
currentVariants = variants;
|
||||
|
||||
// Row 0 is always "(none)" = this cue is off. The numbered variants follow, offset by one.
|
||||
defaultSoundList.Items.Add("(none)");
|
||||
foreach (var v in variants) defaultSoundList.Items.Add(CueSounds.VariantLabel(cue.DefaultFileName, v));
|
||||
|
||||
var chosen = CueSounds.ResolveDefaultFileName(cue.CueId, cue.DefaultFileName, AppConfig.Load());
|
||||
// (none) selected only when the cue is off; otherwise the chosen variant (default = first).
|
||||
var sel = 0;
|
||||
if (chosen is not null)
|
||||
if (cue.LoadEnabled() && variants.Count > 0)
|
||||
{
|
||||
for (var i = 0; i < variants.Count; i++)
|
||||
sel = 1;
|
||||
var chosen = CueSounds.ResolveDefaultFileName(cue.CueId, cue.DefaultFileName, AppConfig.Load());
|
||||
if (chosen is not null)
|
||||
{
|
||||
if (variants[i].Equals(chosen, StringComparison.OrdinalIgnoreCase)) { sel = i; break; }
|
||||
for (var i = 0; i < variants.Count; i++)
|
||||
{
|
||||
if (variants[i].Equals(chosen, StringComparison.OrdinalIgnoreCase)) { sel = i + 1; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
defaultSoundList.SelectedIndex = sel;
|
||||
@@ -759,23 +754,37 @@ internal sealed class PreferencesDialog : Form
|
||||
finally { suppressDefaultSoundPreview = false; }
|
||||
}
|
||||
|
||||
/// <summary>The user arrowed onto / picked a default-sound variant: persist it machine-wide for
|
||||
/// the selected cue and preview it. The running app re-reads the choice when this dialog closes
|
||||
/// (MainForm.ReloadAllCueSounds), so the cue plays the new default from then on.</summary>
|
||||
/// <summary>The user arrowed onto / picked an entry in the sound list. "(none)" (row 0) turns the
|
||||
/// cue off; any other row turns it on and records that variant as the cue's sound, then previews
|
||||
/// it. The running app re-reads it all when the dialog closes (MainForm.ReloadAllCueSounds).</summary>
|
||||
private void OnDefaultSoundChosen()
|
||||
{
|
||||
// Only a genuine user arrow/click should persist + preview; a programmatic re-fill must not.
|
||||
// A programmatic re-fill must not persist or preview.
|
||||
if (suppressDefaultSoundPreview) return;
|
||||
var idx = cueList.SelectedIndex;
|
||||
var vi = defaultSoundList.SelectedIndex;
|
||||
if (idx < 0 || idx >= cueRows.Length || vi < 0 || vi >= currentVariants.Count) return;
|
||||
if (idx < 0 || idx >= cueRows.Length || vi < 0) return;
|
||||
var cue = cueRows[idx];
|
||||
var chosenFile = currentVariants[vi];
|
||||
|
||||
if (vi == 0)
|
||||
{
|
||||
// "(none)" — turn the cue off. No sound to preview.
|
||||
cue.SaveEnabled(false);
|
||||
if (cue.IsProfileSetting) ChangedAnyProfileSetting = true;
|
||||
RefreshCueActionButtons();
|
||||
return;
|
||||
}
|
||||
|
||||
var variantIndex = vi - 1; // account for the (none) row
|
||||
if (variantIndex < 0 || variantIndex >= currentVariants.Count) return;
|
||||
var chosenFile = currentVariants[variantIndex];
|
||||
|
||||
cue.SaveEnabled(true);
|
||||
if (cue.IsProfileSetting) ChangedAnyProfileSetting = true;
|
||||
var cfg = AppConfig.Load();
|
||||
cfg.DefaultCueSounds[cue.CueId] = chosenFile;
|
||||
TrySaveConfig(cfg);
|
||||
RefreshCueActionButtons(); // the Play button now previews this same default
|
||||
RefreshCueActionButtons();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -785,6 +794,23 @@ internal sealed class PreferencesDialog : Form
|
||||
catch { /* a preview must never disturb the dialog */ }
|
||||
}
|
||||
|
||||
/// <summary>Preview the currently-selected cue's current sound (custom or chosen default), so
|
||||
/// arrowing the cue list lets the user hear each cue. A cue set to "(none)" plays nothing.</summary>
|
||||
private void PreviewSelectedCueCurrentSound()
|
||||
{
|
||||
if (suppressDefaultSoundPreview) return;
|
||||
var idx = cueList.SelectedIndex;
|
||||
if (idx < 0 || idx >= cueRows.Length) return;
|
||||
var cue = cueRows[idx];
|
||||
if (!cue.LoadEnabled()) return; // "(none)" — silent
|
||||
try
|
||||
{
|
||||
var path = ResolveCueFilePath(cue);
|
||||
if (path is not null) new CuePlayer(path).Play();
|
||||
}
|
||||
catch { /* a preview must never disturb the dialog */ }
|
||||
}
|
||||
|
||||
/// <summary>Resolves the WAV file currently configured for a cue: the user's custom
|
||||
/// override if set and on disk, otherwise the bundled default in <c>sounds\</c>.
|
||||
/// Returns null when neither resolves to an existing file (typical for save.wav /
|
||||
|
||||
Reference in New Issue
Block a user