using StarTooth.Resources; namespace StarTooth; /// /// Language, colour mode and autostart. Laid out with a TableLayoutPanel throughout so that a /// longer translation grows the dialog instead of being clipped. /// internal sealed class SettingsForm : Form { private readonly Settings _settings; private readonly ComboBox _language = new(); private readonly ComboBox _colorMode = new(); private readonly CheckBox _autostart = new(); /// Display name paired with the value it stands for. private sealed record Choice(string Label, object Value) { public override string ToString() => Label; } internal SettingsForm(Settings settings) { _settings = settings; Text = Strings.SettingsTitle; FormBorderStyle = FormBorderStyle.FixedDialog; StartPosition = FormStartPosition.CenterScreen; MinimizeBox = false; MaximizeBox = false; ShowInTaskbar = true; Icon = TrayIcons.Star; BackColor = Theme.Background; ForeColor = Theme.Foreground; Padding = new Padding(14); // Deliberately not docked. A docked table inherits the form's width before the form has // been sized, and Form.AutoSize cannot widen it afterwards, which collapses the columns. var layout = new TableLayoutPanel { Location = new Point(Padding.Left, Padding.Top), AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, ColumnCount = 2, GrowStyle = TableLayoutPanelGrowStyle.AddRows, }; layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); // Each label is added immediately before its control, so its access key lands on the // right one: a label hands focus to whatever follows it in the tab order. layout.Controls.Add(CreateLabel(Strings.SettingsLanguage), 0, 0); layout.Controls.Add(ConfigureCombo(_language, Strings.SettingsLanguage), 1, 0); layout.Controls.Add(CreateLabel(Strings.SettingsColorMode), 0, 1); layout.Controls.Add(ConfigureCombo(_colorMode, Strings.SettingsColorMode), 1, 1); _autostart.Text = Strings.SettingsAutostart; _autostart.AutoSize = true; _autostart.ForeColor = Theme.Foreground; _autostart.Margin = new Padding(3, 10, 3, 3); _autostart.AccessibleDescription = Strings.SettingsAutostartDescription; layout.Controls.Add(_autostart, 0, 2); layout.SetColumnSpan(_autostart, 2); // The buttons live in the table as well. A second docked panel would leave Form.AutoSize // unable to work out the height it needs, which collapses the dialog. var buttons = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, Anchor = AnchorStyles.Right, Margin = new Padding(3, 14, 3, 3), }; var cancel = CreateButton(Strings.DialogCancel, DialogResult.Cancel); var ok = CreateButton(Strings.DialogOk, DialogResult.OK); buttons.Controls.Add(cancel); buttons.Controls.Add(ok); layout.Controls.Add(buttons, 0, 3); layout.SetColumnSpan(buttons, 2); Controls.Add(layout); AcceptButton = ok; CancelButton = cancel; Populate(); SizeToContent(_language); SizeToContent(_colorMode); // Sized from the measured content, so a longer translation widens the dialog instead of // being cut off. Size preferred = layout.PreferredSize; ClientSize = new Size( preferred.Width + Padding.Horizontal, preferred.Height + Padding.Vertical); } private Label CreateLabel(string text) => new() { Text = text, AutoSize = true, ForeColor = Theme.Foreground, Anchor = AnchorStyles.Left, Margin = new Padding(3, 7, 8, 3), }; private ComboBox ConfigureCombo(ComboBox combo, string label) { combo.DropDownStyle = ComboBoxStyle.DropDownList; // AutoSize would discard the width, leaving the box collapsed in an AutoSize table. // The real width is measured from the items once they exist, see SizeToContent. combo.AutoSize = false; combo.Anchor = AnchorStyles.Left; combo.Margin = new Padding(3, 4, 3, 4); combo.FlatStyle = Theme.IsDark ? FlatStyle.Flat : FlatStyle.Standard; combo.BackColor = Theme.IsDark ? Color.FromArgb(45, 45, 45) : Color.White; combo.ForeColor = Theme.Foreground; // Without the ampersand, so the access key is not spoken as part of the name. combo.AccessibleName = label.Replace("&", string.Empty); return combo; } private Button CreateButton(string text, DialogResult result) => new() { Text = text, DialogResult = result, AutoSize = true, BackColor = Theme.Highlight, ForeColor = Theme.Foreground, FlatStyle = Theme.IsDark ? FlatStyle.Flat : FlatStyle.Standard, Margin = new Padding(6, 0, 0, 0), }; /// Widens the box to its longest entry, so no translation gets cut off. private static void SizeToContent(ComboBox combo) { int widest = 0; foreach (object item in combo.Items) widest = Math.Max(widest, TextRenderer.MeasureText(item.ToString(), combo.Font).Width); combo.Width = widest + SystemInformation.VerticalScrollBarWidth + 12; } private void Populate() { // Languages are listed under their own names, as language pickers conventionally do. _language.Items.Add(new Choice(Strings.SettingsLanguageSystem, string.Empty)); _language.Items.Add(new Choice("Deutsch", "de")); _language.Items.Add(new Choice("English", "en")); _language.SelectedIndex = _settings.Language switch { "de" => 1, "en" => 2, _ => 0, }; _colorMode.Items.Add(new Choice(Strings.SettingsColorModeSystem, ThemeMode.System)); _colorMode.Items.Add(new Choice(Strings.SettingsColorModeLight, ThemeMode.Light)); _colorMode.Items.Add(new Choice(Strings.SettingsColorModeDark, ThemeMode.Dark)); _colorMode.SelectedIndex = _settings.Theme switch { ThemeMode.Light => 1, ThemeMode.Dark => 2, _ => 0, }; _autostart.Checked = Autostart.IsEnabled; } protected override void OnFormClosing(FormClosingEventArgs e) { if (DialogResult != DialogResult.OK) { base.OnFormClosing(e); return; } if (_autostart.Checked != Autostart.IsEnabled && !Autostart.TrySet(_autostart.Checked)) { MessageBox.Show( this, Strings.SettingsAutostartFailedText, Strings.SettingsAutostartFailedTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); // Keep the dialog open rather than reporting a change that did not happen. _autostart.Checked = Autostart.IsEnabled; e.Cancel = true; return; } _settings.Language = (string)((Choice)_language.SelectedItem!).Value; _settings.Theme = (ThemeMode)((Choice)_colorMode.SelectedItem!).Value; _settings.Save(); base.OnFormClosing(e); } }