I18NCommander/I18N Commander/UI WinForms/Components/Setting.cs

356 lines
16 KiB
C#

using DataModel.Database;
using Processor;
using UI_WinForms.Resources;
namespace UI_WinForms.Components;
public sealed partial class Setting : UserControl
{
private SettingUIData data;
public Setting()
{
this.InitializeComponent();
}
private Setting(SettingUIData settingMetaData)
{
this.InitializeComponent();
this.Dock = DockStyle.Top;
this.data = settingMetaData;
this.labelIcon.Image = settingMetaData.Icon;
this.labelSettingName.Text = settingMetaData.SettingName();
this.labelExplanation.Text = settingMetaData.SettingExplanation();
var dataControl = settingMetaData.SetupDataControl();
this.tableLayout.Controls.Add(dataControl, 2, 0);
// Ensure, that this data control is vertical centered by calculating the needed margin, considering the outer size of the table layout:
var margin = (this.tableLayout.GetRowHeights().First() - dataControl.Height) / 2f;
dataControl.Margin = new Padding(0, (int) margin, 0, (int)margin);
// Calculate the needed height of the explanation label & centering of the data control when the parent window is resized:
this.tableLayout.Resize += (sender, args) =>
{
// Adjust the height of the parent controls (table & user control):
this.tableLayout.Height = Math.Max((int)this.labelExplanation.CreateGraphics().MeasureString(this.labelExplanation.Text, this.labelExplanation.Font, new SizeF(this.labelExplanation.Width, 1000)).Height, 66);
this.Height = this.tableLayout.Height + this.tableLayout.Margin.Vertical;
// Ensure, that this data control is vertical centered by calculating the needed margin, considering the outer size of the table layout:
var margin = (this.tableLayout.GetRowHeights().First() - dataControl.Height) / 2f;
dataControl.Margin = new Padding(0, (int) margin, 0, (int)margin);
};
}
private void UpdateExplanation() => this.labelExplanation.Text = this.data.SettingExplanation();
private readonly record struct SettingUIData(
Bitmap Icon,
Func<string> SettingName,
Func<string> SettingExplanation,
Func<Control> SetupDataControl
);
private static async Task<Setting> ShowDeepLModeSettingAsync()
{
var currentSetting = await AppSettings.GetDeepLMode();
var settingData = new SettingUIData(
Icon: Icons.deepl_logo_icon_170284,
SettingName: () => "DeepL Service",
SettingExplanation: () => "DeepL is a translation service that offers a wide range of translation services. This setting allows you to choose between the free and pro version of DeepL.",
SetupDataControl: () =>
{
var dropdown = new ComboBox();
dropdown.Items.Add("Disabled");
dropdown.Items.Add("Free version");
dropdown.Items.Add("Pro version");
dropdown.SelectedIndex = currentSetting switch
{
SettingDeepLMode.DISABLED => 0,
SettingDeepLMode.USE_FREE_ACCOUNT => 1,
SettingDeepLMode.USE_PRO_ACCOUNT => 2,
_ => 0,
};
// Setup the change event handler:
dropdown.SelectedValueChanged += async (sender, args) => await AppSettings.SetDeepLMode(dropdown.SelectedIndex switch
{
0 => SettingDeepLMode.DISABLED,
1 => SettingDeepLMode.USE_FREE_ACCOUNT,
2 => SettingDeepLMode.USE_PRO_ACCOUNT,
_ => SettingDeepLMode.DISABLED,
});
// Apply the desired layout:
dropdown.Dock = DockStyle.Fill;
dropdown.DropDownStyle = ComboBoxStyle.DropDownList;
return dropdown;
}
);
return new Setting(settingData);
}
private static async Task<Setting> ShowDeepLAPIKeySettingAsync()
{
var currentSetting = await AppSettings.GetDeepLAPIKey();
var settingData = new SettingUIData(
Icon: Icons.icons8_key_512,
SettingName: () => "DeepL API Key",
SettingExplanation: () => "The API key is required to use the DeepL translation service. You can find your API key on the DeepL website.",
SetupDataControl: () =>
{
var textbox = new TextBox();
textbox.Text = currentSetting;
textbox.TextChanged += async (sender, args) => await AppSettings.SetDeepLAPIKey(textbox.Text);
textbox.Dock = DockStyle.Fill;
return textbox;
}
);
return new Setting(settingData);
}
private static async Task<Setting> ShowDeepLUsageSettingAsync()
{
var currentUsage = await Processor.DeepL.GetUsage();
var percent = currentUsage.Enabled ? currentUsage.CharacterCount / (float)currentUsage.CharacterLimit : 0;
// Local function to show & update the explanation text:
string GetUsageText() => currentUsage.Enabled ?
$"You used {currentUsage.CharacterCount:###,###,###,##0} characters out of {currentUsage.CharacterLimit:###,###,###,##0} ({percent:P2})." : currentUsage.AuthIssue ?
"Was not able to authorize with DeepL. Please check your API key." :
"DeepL is disabled or the API key is not set.";
var settingData = new SettingUIData(
Icon: Icons.icons8_increase_512,
SettingName: () => "DeepL Usage",
SettingExplanation: GetUsageText,
SetupDataControl: () =>
{
var progressbar = new ProgressBar();
progressbar.Maximum = 100;
progressbar.Margin = new Padding(0, 16, 0, 16);
progressbar.Dock = DockStyle.Fill;
progressbar.Style = ProgressBarStyle.Continuous;
progressbar.Value = percent switch
{
< 0 => 0,
> 1 => 100,
_ => (int)(percent * 100)
};
var reloadButton = new Button();
reloadButton.Text = string.Empty;
reloadButton.Image = Icons.icons8_reload_512;
reloadButton.FlatStyle = FlatStyle.Flat;
reloadButton.FlatAppearance.BorderSize = 0;
reloadButton.BackColor = Color.Empty;
reloadButton.UseVisualStyleBackColor = true;
reloadButton.Size = new Size(60, 60);
reloadButton.Click += async (sender, args) =>
{
var usage = await Processor.DeepL.GetUsage();
// Update the outer variables:
percent = usage.Enabled ? usage.CharacterCount / (float)usage.CharacterLimit : 0;
currentUsage = usage;
// Update the progress bar:
progressbar.Value = percent switch
{
< 0 => 0,
> 1 => 100,
_ => (int)(percent * 100)
};
// Update the explanation text. Therefore, we need to get the setting object through the chain of parents:
var setting = (Setting) ((Control) sender).Parent.Parent.Parent;
setting.UpdateExplanation();
};
// Setup the layout:
var layout = new TableLayoutPanel();
layout.ColumnCount = 2;
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 66));
layout.RowCount = 1;
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
layout.Controls.Add(progressbar, 0, 0);
layout.Controls.Add(reloadButton, 1, 0);
layout.Dock = DockStyle.Fill;
return layout;
}
);
var setting = new Setting(settingData);
return setting;
}
private static async Task<Setting> ShowDeepLActionSettingAsync()
{
var currentSetting = await AppSettings.GetDeepLAction();
var settingData = new SettingUIData(
Icon: Icons.icons8_play_512__2_,
SettingName: () => "DeepL Operation",
SettingExplanation: () => "Should the missing translations be automatically completed by DeepL? This can lead to higher costs. By default, DeepL is only applied manually.",
SetupDataControl: () =>
{
// We set up a combo box with the available actions:
var dropdown = new ComboBox();
dropdown.Items.Add("Manual");
dropdown.Items.Add("Automatic");
dropdown.SelectedIndex = currentSetting switch
{
SettingDeepLAction.MANUAL => 0,
SettingDeepLAction.AUTOMATIC_ALL => 1,
_ => 0,
};
// Setup the change event handler:
dropdown.SelectedValueChanged += async (sender, args) => await AppSettings.SetDeepLAction(dropdown.SelectedIndex switch
{
0 => SettingDeepLAction.MANUAL,
1 => SettingDeepLAction.AUTOMATIC_ALL,
_ => SettingDeepLAction.MANUAL,
});
// Apply the desired layout:
dropdown.Dock = DockStyle.Fill;
dropdown.DropDownStyle = ComboBoxStyle.DropDownList;
return dropdown;
}
);
return new Setting(settingData);
}
internal readonly record struct ComboBoxItem(string DisplayText, int CultureIndex)
{
public override string ToString() => this.DisplayText;
}
private static async Task<Setting> ShowDeepLSourceCultureSettingAsync()
{
var currentSourceCultureIndex = await AppSettings.GetDeepLSourceCultureIndex();
// We load the corresponding culture for that index. As dropdown items, we show
// all other available cultures:
var allCultures = await AppSettings.GetCultureInfos();
// Attention: We have to store the culture's index, because the index is not
// continuous and can change when the user adds or removes a culture!
var settingData = new SettingUIData(
Icon: Icons.icons8_chat_bubble_512,
SettingName: () => "DeepL Source Culture",
SettingExplanation: () => "The source culture is used to translate the missing translations.",
SetupDataControl: () =>
{
var dropdown = new ComboBox();
var currentCultureDropdownIndex = 0;
for (var n = 0; n < allCultures.Count; n++)
{
var cultureInfo = allCultures[n];
if(cultureInfo.Index == currentSourceCultureIndex)
currentCultureDropdownIndex = n;
dropdown.Items.Add(new ComboBoxItem($"{cultureInfo.Index}.: {cultureInfo.Code}", cultureInfo.Index));
}
dropdown.SelectedIndex = currentCultureDropdownIndex;
// Setup the change event handler:
dropdown.SelectedValueChanged += async (sender, args) =>
{
if(dropdown.SelectedItem is ComboBoxItem selectedItem)
await AppSettings.SetDeepLSourceCultureIndex(selectedItem.CultureIndex);
};
// Apply the desired layout:
dropdown.Dock = DockStyle.Fill;
dropdown.DropDownStyle = ComboBoxStyle.DropDownList;
return dropdown;
}
);
return new Setting(settingData);
}
private static IEnumerable<Task<Setting>> ShowCultureSettingsAsync()
{
var isFirstCulture = true; // We need this flag to distinguish the first task from the others.
var cultureIndices = new List<int>(new []{ -1 });
while (cultureIndices.Count > 0)
{
var innerLoopIndex = cultureIndices.Last(); // needed to avoid closure issues.
yield return Task.Run(async () =>
{
var localCultureIndex = innerLoopIndex;
// Get a list of culture indices. Thus, we know the number of cultures. We cannot do this in the outer loop,
// because we cannot await there. The AppSettings is caching the answer, though. The list of indices is ordered
// ascending.
var localCultureIndices = await AppSettings.GetCultureIndices();
// Update the number of cultures in the outer loop for the first call:
if(isFirstCulture)
{
localCultureIndex = localCultureIndices.Last();
innerLoopIndex = localCultureIndices.Last();
cultureIndices.Clear();
cultureIndices.AddRange(localCultureIndices);
isFirstCulture = false;
}
// Get the current culture code:
var currentCultureCode = await AppSettings.GetCultureCode(localCultureIndex);
// Construct the setting:
return new Setting(new()
{
Icon = Icons.icons8_chat_bubble_512,
SettingName = () => $"{localCultureIndex}. Culture",
SettingExplanation = () => "The culture according to RFC 4646: First comes the ISO 639-1 language code in lower case, followed by a hyphen, followed by the ISO 3166-1 alpha-2 country code in upper case. Example: en-US for English in the USA, de-DE for German in Germany.",
SetupDataControl = () =>
{
var textbox = new TextBox();
textbox.Text = currentCultureCode;
textbox.TextChanged += async (sender, args) =>
{
await AppSettings.SetCultureCode(localCultureIndex, textbox.Text);
};
textbox.Dock = DockStyle.Fill;
return textbox;
}
});
});
cultureIndices.Remove(innerLoopIndex);
}
}
public static IEnumerable<Task<Setting>> GetAllSettings()
{
yield return ShowDeepLSourceCultureSettingAsync();
foreach (var setting in ShowCultureSettingsAsync())
{
yield return setting;
}
yield return ShowDeepLUsageSettingAsync();
yield return ShowDeepLActionSettingAsync();
yield return ShowDeepLAPIKeySettingAsync();
yield return ShowDeepLModeSettingAsync();
}
}