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

300 lines
12 KiB
C#

using DataModel.Database;
using Processor;
using UI_WinForms.Dialogs;
using UI_WinForms.Resources;
namespace UI_WinForms.Components;
public partial class SectionTree : UserControl
{
private static readonly Dictionary<TreeNode, EventHandler<DataModel.Database.Translation?>> NODE_PROGRESS_HANDLERS = new();
public SectionTree()
{
this.InitializeComponent();
// Check if we are in the designer:
if(Program.SERVICE_PROVIDER is null)
return;
// Create an image list from a resource:
var imgList = new ImageList();
imgList.ImageSize = new Size(45, 45);
imgList.ColorDepth = ColorDepth.Depth32Bit;
imgList.Images.Add(Icons.icons8_documents_folder_512);
// Set the image list to the tree view:
this.treeView.ImageList = imgList;
// Subscribe to the load event:
this.Load += this.LoadNodes;
this.Load += (sender, args) => this.SetupGeneratorButton();
}
private async void SetupGeneratorButton()
{
this.buttonGenerate.Enabled = false;
// Depend the generator button's visibility on the generator settings:
this.buttonGenerate.Enabled = await AppSettings.GetGeneratorDotnetEnabled() || await AppSettings.GetGeneratorGodotEnabled();
// Subscribe to the changed settings event:
AppEvents.WhenSettingsChanged += async (sender, args) =>
{
this.buttonGenerate.Enabled = await AppSettings.GetGeneratorDotnetEnabled() || await AppSettings.GetGeneratorGodotEnabled();
};
}
private async void LoadNodes(object? sender, EventArgs e)
{
if(this.DesignMode)
return;
// A dictionary to cache all known tree nodes:
var treeNodes = new Dictionary<string, TreeNode>();
// Get the max. depth of the tree:
var maxDepth = await SectionProcessor.GetDepth();
// Store nodes, where we cannot find the parents:
var missingParents = new List<TreeNode>();
// Populate the tree view out of the database, layer by layer:
for (var i = 0; i <= maxDepth; i++)
{
foreach (var section in await SectionProcessor.LoadLayer(i))
{
var translationProgress = await TranslationProcessor.CalculateTranslationProgress(section);
// Create the tree node:
var node = new TreeNode
{
Name = section.DataKey, // [sic] name is the key
Text = $"{section.Name} [{translationProgress.Result}%]",
StateImageIndex = 1,
};
// Cache the node:
treeNodes.Add(section.DataKey, node);
// Wire each node to the translation change event to update the translation progress:
NODE_PROGRESS_HANDLERS.Add(node, async (_, translation) => await this.UpdateTranslationProgress(node, section, translation));
AppEvents.WhenTranslationChanged += NODE_PROGRESS_HANDLERS[node];
// Is this a root node?
if (section.Depth is 0)
// Set the root node:
this.treeView.Nodes.Add(node);
// Otherwise, attach this section to its parent node:
else
{
// Get the parent from our cache within O(1):
treeNodes.TryGetValue(section.Parent?.DataKey ?? string.Empty, out var parent);
// If the parent node is not found, skip this section:
if (parent is null)
{
missingParents.Add(node);
continue;
}
// Add the node to the parent:
parent.Nodes.Add(node);
}
}
}
// If we found any missing parents, show a dialog:
if (missingParents.Any())
{
MessageBox.Show($"In {missingParents.Count} case(s) we could not found the matching parent. We added these nodes to a special root node, though.", "Parent not found", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Create a root node for all missing parents:
var rootMissedParents = new TreeNode
{
Name = "MISSING_PARENTS",
Text = "Missing Parents",
StateImageIndex = 1,
};
// Add the root node to the tree:
this.treeView.Nodes.Add(rootMissedParents);
// Add all missing parents to the root node:
foreach (var node in missingParents)
rootMissedParents.Nodes.Add(node);
}
// Expand the tree:
this.treeView.ExpandAll();
}
private async Task UpdateTranslationProgress(TreeNode node, Section section, DataModel.Database.Translation? translation)
{
if (translation is null || translation.TextElement.Section.DataKey == section.DataKey)
{
var currentTranslationProgress = await TranslationProcessor.CalculateTranslationProgress(section);
if(this.treeView.InvokeRequired)
this.treeView.Invoke(() => node.Text = $"{section.Name} [{currentTranslationProgress.Result}%]");
else
node.Text = $"{section.Name} [{currentTranslationProgress.Result}%]";
}
}
private async void buttonAdd_Click(object sender, EventArgs e)
{
if(this.DesignMode)
return;
var result = InputDialog.Show(new InputDialog.Options(
Message: "Please type the desired section name.",
Title: "Add a section",
Placeholder: "My next section",
ShowQuestionCheckbox: true,
QuestionCheckboxText: "Add a root node (i.e. ignoring the selected node)"
));
if(result.DialogResult == DialogResult.Cancel)
return;
var addRootNode = result.AnswerToQuestion;
// Get the currently selected section as parent:
var selectedNode = this.treeView.SelectedNode;
// Add the new section to the database:
var addedSection = await SectionProcessor.AddSection(result.Text, addRootNode ? null : selectedNode?.Name);
addedSection.ProcessError();
if(!addedSection.Successful)
return;
var translationProgress = await TranslationProcessor.CalculateTranslationProgress(addedSection.Result!);
// Add the new section to the tree control:
var node = new TreeNode
{
Name = addedSection.Result!.DataKey, // [sic] name is the key
Text = $"{addedSection.Result.Name} [{translationProgress.Result}%]",
StateImageIndex = 1,
};
// Wire each node to the translation change event to update the translation progress:
NODE_PROGRESS_HANDLERS.Add(node, async (_, translation) => await this.UpdateTranslationProgress(node, addedSection.Result, translation));
AppEvents.WhenTranslationChanged += NODE_PROGRESS_HANDLERS[node];
if(!addRootNode && selectedNode is not null)
selectedNode.Nodes.Add(node);
else
this.treeView.Nodes.Add(node);
// Ensure, that the added node is visible and gets the focus:
node.EnsureVisible();
this.treeView.SelectedNode = node;
// Fire the click event:
this.treeView_NodeMouseClick(this, new TreeNodeMouseClickEventArgs(node, MouseButtons.Left, 1, 0, 0));
}
private async void buttonRemove_Click(object sender, EventArgs e)
{
if(this.DesignMode)
return;
// Get the currently selected section, which will be removed:
var selectedNode = this.treeView.SelectedNode;
// Get the number of children:
// (notice, that the node's name is its key)
var numberChildren = await SectionProcessor.NumberChildren(selectedNode.Name);
// Ask the user, if he really wants to remove the section:
if(MessageBox.Show(numberChildren > 0 ? $"Are you sure, you want to remove the section '{selectedNode.Text}', its {numberChildren} children and so on?" : $"Are you sure, you want to remove the section '{selectedNode.Text}'?", "Remove section", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.No)
return;
// Remove the section from the database:
// (notice, that the node's name is its key)
await SectionProcessor.RemoveSection(selectedNode.Name);
// Remove all nodes from the tree control:
this.treeView.Nodes.Clear();
foreach (var (treeNode, handler) in NODE_PROGRESS_HANDLERS)
AppEvents.WhenTranslationChanged -= handler;
NODE_PROGRESS_HANDLERS.Clear();
// Reload the tree:
this.LoadNodes(this, EventArgs.Empty);
// Select the first root node:
this.treeView.SelectedNode = this.treeView.Nodes.Count > 0 ? this.treeView.Nodes[0] : null;
// Fire the click event:
this.treeView_NodeMouseClick(this, new TreeNodeMouseClickEventArgs(this.treeView.SelectedNode!, MouseButtons.Left, 1, 0, 0));
AppEvents.TranslationChanged(null);
}
private async void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
if(this.DesignMode)
return;
// Get the currently selected section:
var selectedNode = e.Node;
// If the selected node is not null, enable the remove & edit button:
this.buttonRename.Enabled = this.buttonRemove.Enabled = selectedNode is not null;
// When a section is selected, fire the event:
if (selectedNode is not null)
{
// Get the section from the database:
var section = await SectionProcessor.GetSection(selectedNode.Name);
AppEvents.SectionChanged(section);
}
}
private async void buttonRename_Click(object sender, EventArgs e)
{
if(this.DesignMode)
return;
// Ask the user if he really wants to rename the section:
if(MessageBox.Show("Are you sure, you want to rename the selected section? If you are already using this section in your code, you will need to manually refactor your code after renaming it.", "Rename section", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.No)
return;
// Get the currently selected section:
var selectedNode = this.treeView.SelectedNode;
// Ask the user for the new name:
var result = InputDialog.Show(new InputDialog.Options(
Message: "Please edit the section name.",
PreloadedText: selectedNode.Text,
ShowQuestionCheckbox: false,
Title: "Rename section"
));
// If the user canceled, return:
if(result.DialogResult == DialogResult.Cancel)
return;
// Rename the section:
var alteredSection = await SectionProcessor.RenameSection(selectedNode.Name, result.Text);
alteredSection.ProcessError();
if(!alteredSection.Successful)
return;
// Rename the selected node:
selectedNode.Text = alteredSection.Result!.Name;
selectedNode.Name = alteredSection.Result.DataKey; // [sic] name is the key
}
private void buttonGenerate_Click(object sender, EventArgs e)
{
}
}