using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Gtk;
using UI = Gtk.Builder.ObjectAttribute;
/// Controller for notebook page 2 (SoundBox). Not a Gtk.Window — same
/// arrangement as SettingsWindow: it drives widgets inside the shared root.
public class SoundWindow
{
[UI] private Label ConditionNameLabel = null;
[UI] private Grid SoundButtonGrid = null;
[UI] private Button SoundBackButton = null;
private const int Columns = 2;
private readonly List<CaseDefinition> _allCases;
private readonly List<string> _path = new List<string>();
/// Raised when the drill-down reaches a leaf case.
public event EventHandler<CaseDefinition> CaseSelected;
/// Raised when Back is pressed at the top level.
public event EventHandler BackRequested;
public SoundWindow(Builder builder)
{
builder.Autoconnect(this);
if (ConditionNameLabel == null || SoundButtonGrid == null || SoundBackButton == null)
throw new InvalidOperationException(
"Glade id mismatch — ConditionNameLabel=" + (ConditionNameLabel != null) +
" SoundButtonGrid=" + (SoundButtonGrid != null) +
" SoundBackButton=" + (SoundBackButton != null));
_allCases = CaseDefinition.LoadCasesFromCsv(AppFile("cases.csv"));
if (_allCases.Count == 0)
Console.WriteLine("WARNING: no cases loaded — check cases.csv");
SoundBackButton.Clicked += OnBackClicked;
}
/// Call every time the page becomes visible.
public void Reset()
{
_path.Clear();
Render();
}
/// Resolve next to the .exe, NOT the working directory.
public static string AppFile(string relative)
{
string dir = Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
return Path.Combine(dir, relative);
}
// ── one method covers all three levels ─────────────────────────────
private void Render()
{
var matches = _allCases.Where(MatchesPath).ToList();
// Distinct values one level deeper than where we currently are.
var options = matches
.Where(c => c.TreePath.Length > _path.Count)
.Select(c => c.TreePath[_path.Count])
.Distinct()
.ToList();
ConditionNameLabel.Text = _path.Count == 0
? "種別を選択"
: string.Join(" : ", _path);
BuildButtons(options, picked =>
{
_path.Add(picked);
// Landed on a leaf? Play it and stay put.
var leaf = _allCases.FirstOrDefault(
c => c.TreePath.Length == _path.Count && MatchesPath(c));
if (leaf != null)
{
Play(leaf);
_path.RemoveAt(_path.Count - 1);
return;
}
Render();
});
}
private bool MatchesPath(CaseDefinition c)
{
string[] p = c.TreePath;
if (p.Length < _path.Count) return false;
for (int i = 0; i < _path.Count; i++)
if (!string.Equals(p[i], _path[i], StringComparison.Ordinal)) return false;
return true;
}
private void BuildButtons(IEnumerable<string> labels, Action<string> onPick)
{
foreach (var child in SoundButtonGrid.Children)
{
SoundButtonGrid.Remove(child);
child.Destroy();
}
int i = 0;
foreach (string text in labels)
{
string captured = text; // don't close over the loop variable
var btn = new Button(captured);
btn.Hexpand = true;
btn.Clicked += (s, e) => onPick(captured);
SoundButtonGrid.Attach(btn, i % Columns, i / Columns, 1, 1);
i++;
}
SoundButtonGrid.ShowAll(); // widgets made in code start hidden
}
private void Play(CaseDefinition c)
{
if (string.IsNullOrWhiteSpace(c.SoundPath))
{
Console.WriteLine("No Sound_File for case " + c.Number);
return;
}
WavePlayer.Stop(); // the map page owns audio from here on
var h = CaseSelected;
if (h != null) h(this, c);
// string full = AppFile(c.SoundPath); // "sound/SND200.wav" -> absolute
// if (!File.Exists(full))
// {
// Console.WriteLine("Sound file missing: " + full);
// return;
// }
// ConditionNameLabel.Text = string.Join(" : ", _path);
// WavePlayer.Play(full);
}
private void OnBackClicked(object sender, EventArgs e)
{
if (_path.Count > 0)
{
_path.RemoveAt(_path.Count - 1);
Render();
}
else
{
WavePlayer.Stop();
if (BackRequested != null) BackRequested(this, EventArgs.Empty);
}
}
}