356 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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();
 | 
						|
    }
 | 
						|
} |