From aa641770d276d6b6fd2ea8399cb4a0a1ae4dc45a Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 10 Jul 2022 19:30:22 +0200 Subject: [PATCH 01/43] Added a name field to text elements --- .../DataModel/Database/Common/DataContext.cs | 1 + .../DataModel/Database/TextElement.cs | 4 +- ...72935_202207AddTextElementName.Designer.cs | 201 ++++++++++++++++++ ...20220710172935_202207AddTextElementName.cs | 35 +++ .../Migrations/DataContextModelSnapshot.cs | 6 + 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.Designer.cs create mode 100644 I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.cs diff --git a/I18N Commander/DataModel/Database/Common/DataContext.cs b/I18N Commander/DataModel/Database/Common/DataContext.cs index 08fe633..eee9081 100644 --- a/I18N Commander/DataModel/Database/Common/DataContext.cs +++ b/I18N Commander/DataModel/Database/Common/DataContext.cs @@ -44,6 +44,7 @@ public sealed class DataContext : DbContext modelBuilder.Entity().HasIndex(n => n.Id); modelBuilder.Entity().HasIndex(n => n.Code); + modelBuilder.Entity().HasIndex(n => n.Name); #endregion diff --git a/I18N Commander/DataModel/Database/TextElement.cs b/I18N Commander/DataModel/Database/TextElement.cs index 2135ffe..43fe3c0 100644 --- a/I18N Commander/DataModel/Database/TextElement.cs +++ b/I18N Commander/DataModel/Database/TextElement.cs @@ -7,9 +7,11 @@ public sealed class TextElement [Key] public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Code { get; set; } = string.Empty; public Section Section { get; set; } - public List Translations { get; set; } + public List Translations { get; set; } = new(); } \ No newline at end of file diff --git a/I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.Designer.cs b/I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.Designer.cs new file mode 100644 index 0000000..f3b9a47 --- /dev/null +++ b/I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.Designer.cs @@ -0,0 +1,201 @@ +// +using System; +using DataModel.Database.Common; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DataModel.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20220710172935_202207AddTextElementName")] + partial class _202207AddTextElementName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.5"); + + modelBuilder.Entity("DataModel.Database.Section", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DataKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Depth") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DataKey"); + + b.HasIndex("Depth"); + + b.HasIndex("Id"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.ToTable("Sections"); + }); + + modelBuilder.Entity("DataModel.Database.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BoolValue") + .HasColumnType("INTEGER"); + + b.Property("GuidValue") + .HasColumnType("TEXT"); + + b.Property("IntegerValue") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TextValue") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BoolValue"); + + b.HasIndex("GuidValue"); + + b.HasIndex("Id"); + + b.HasIndex("IntegerValue"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("TextValue"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("DataModel.Database.TextElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SectionId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("Id"); + + b.HasIndex("Name"); + + b.HasIndex("SectionId"); + + b.ToTable("TextElements"); + }); + + modelBuilder.Entity("DataModel.Database.Translation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Culture") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Text") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TextElementId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Culture"); + + b.HasIndex("Id"); + + b.HasIndex("Text"); + + b.HasIndex("TextElementId"); + + b.ToTable("Translations"); + }); + + modelBuilder.Entity("DataModel.Database.Section", b => + { + b.HasOne("DataModel.Database.Section", "Parent") + .WithMany() + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("DataModel.Database.TextElement", b => + { + b.HasOne("DataModel.Database.Section", "Section") + .WithMany("TextElements") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("DataModel.Database.Translation", b => + { + b.HasOne("DataModel.Database.TextElement", "TextElement") + .WithMany("Translations") + .HasForeignKey("TextElementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TextElement"); + }); + + modelBuilder.Entity("DataModel.Database.Section", b => + { + b.Navigation("TextElements"); + }); + + modelBuilder.Entity("DataModel.Database.TextElement", b => + { + b.Navigation("Translations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.cs b/I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.cs new file mode 100644 index 0000000..f21f4ea --- /dev/null +++ b/I18N Commander/DataModel/Migrations/20220710172935_202207AddTextElementName.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DataModel.Migrations +{ + public partial class _202207AddTextElementName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Name", + table: "TextElements", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateIndex( + name: "IX_TextElements_Name", + table: "TextElements", + column: "Name"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_TextElements_Name", + table: "TextElements"); + + migrationBuilder.DropColumn( + name: "Name", + table: "TextElements"); + } + } +} diff --git a/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs b/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs index 6a285f4..616d211 100644 --- a/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs +++ b/I18N Commander/DataModel/Migrations/DataContextModelSnapshot.cs @@ -103,6 +103,10 @@ namespace DataModel.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("SectionId") .HasColumnType("INTEGER"); @@ -112,6 +116,8 @@ namespace DataModel.Migrations b.HasIndex("Id"); + b.HasIndex("Name"); + b.HasIndex("SectionId"); b.ToTable("TextElements"); From 3dfaccc99fad8946d075bd702661eb4a6abc1231 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 10 Jul 2022 20:30:28 +0200 Subject: [PATCH 02/43] Added detection for design mode --- .../UI WinForms/Components/LoaderStart.cs | 6 ++++++ .../UI WinForms/Components/SectionTree.cs | 19 ++++++++++++++++++- .../UI WinForms/Dialogs/InputDialog.cs | 3 +++ I18N Commander/UI WinForms/Loader.cs | 3 +++ I18N Commander/UI WinForms/Main.Designer.cs | 8 ++++---- 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/LoaderStart.cs b/I18N Commander/UI WinForms/Components/LoaderStart.cs index 052fe29..9a18582 100644 --- a/I18N Commander/UI WinForms/Components/LoaderStart.cs +++ b/I18N Commander/UI WinForms/Components/LoaderStart.cs @@ -62,6 +62,9 @@ public partial class LoaderStart : UserControl private void buttonOpen_Click(object sender, EventArgs e) { + if(this.DesignMode) + return; + if(this.areRecentProjectsVisible) { this.areRecentProjectsVisible = false; @@ -91,6 +94,9 @@ public partial class LoaderStart : UserControl private void buttonNew_Click(object sender, EventArgs e) { + if(this.DesignMode) + return; + var saveDialog = new SaveFileDialog { AddExtension = true, diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs index a3479b3..196facd 100644 --- a/I18N Commander/UI WinForms/Components/SectionTree.cs +++ b/I18N Commander/UI WinForms/Components/SectionTree.cs @@ -13,7 +13,9 @@ public partial class SectionTree : UserControl public SectionTree() { this.InitializeComponent(); - + if(this.DesignMode) + return; + // Get the DI context from the main form: this.db = Program.SERVICE_PROVIDER.GetService()!; @@ -35,6 +37,9 @@ public partial class SectionTree : UserControl private async void LoadNodes(object? sender, EventArgs e) { + if(this.DesignMode) + return; + // A dictionary to cache all known tree nodes: var treeNodes = new Dictionary(); @@ -112,6 +117,9 @@ public partial class SectionTree : UserControl 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", @@ -151,6 +159,9 @@ public partial class SectionTree : UserControl 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; @@ -175,6 +186,9 @@ public partial class SectionTree : UserControl private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { + if(this.DesignMode) + return; + // Get the currently selected section: var selectedNode = this.treeView.SelectedNode; @@ -184,6 +198,9 @@ public partial class SectionTree : UserControl 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; diff --git a/I18N Commander/UI WinForms/Dialogs/InputDialog.cs b/I18N Commander/UI WinForms/Dialogs/InputDialog.cs index 25afee0..eb92d63 100644 --- a/I18N Commander/UI WinForms/Dialogs/InputDialog.cs +++ b/I18N Commander/UI WinForms/Dialogs/InputDialog.cs @@ -48,6 +48,9 @@ public partial class InputDialog : Form private void buttonOk_Click(object sender, EventArgs e) { + if(this.DesignMode) + return; + if (!string.IsNullOrWhiteSpace(this.textBoxInput.Text)) { this.DialogResult = DialogResult.OK; diff --git a/I18N Commander/UI WinForms/Loader.cs b/I18N Commander/UI WinForms/Loader.cs index 38065c7..6a6ca87 100644 --- a/I18N Commander/UI WinForms/Loader.cs +++ b/I18N Commander/UI WinForms/Loader.cs @@ -11,6 +11,9 @@ public partial class Loader : Form private void loaderStart_LoadProject(object sender, string projectFilePath) { + if(this.DesignMode) + return; + this.DataFile = projectFilePath; this.DialogResult = DialogResult.OK; this.Close(); diff --git a/I18N Commander/UI WinForms/Main.Designer.cs b/I18N Commander/UI WinForms/Main.Designer.cs index 3189ccc..2944fd8 100644 --- a/I18N Commander/UI WinForms/Main.Designer.cs +++ b/I18N Commander/UI WinForms/Main.Designer.cs @@ -3,12 +3,12 @@ partial class Main { /// - /// Required designer variable. + /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// - /// Clean up any resources being used. + /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) @@ -23,8 +23,8 @@ #region Windows Form Designer generated code /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. /// private void InitializeComponent() { From f1fc7825062fab80097e9aa1316757797934f92a Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 11 Jul 2022 17:58:37 +0200 Subject: [PATCH 03/43] Fixed WinForms designer issue --- I18N Commander/UI WinForms/Components/SectionTree.cs | 4 +++- I18N Commander/UI WinForms/Program.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs index 196facd..02ede17 100644 --- a/I18N Commander/UI WinForms/Components/SectionTree.cs +++ b/I18N Commander/UI WinForms/Components/SectionTree.cs @@ -13,7 +13,9 @@ public partial class SectionTree : UserControl public SectionTree() { this.InitializeComponent(); - if(this.DesignMode) + + // Check if we are in the designer: + if(Program.SERVICE_PROVIDER is null) return; // Get the DI context from the main form: diff --git a/I18N Commander/UI WinForms/Program.cs b/I18N Commander/UI WinForms/Program.cs index 5c21401..a26755f 100644 --- a/I18N Commander/UI WinForms/Program.cs +++ b/I18N Commander/UI WinForms/Program.cs @@ -8,7 +8,7 @@ namespace UI_WinForms; internal static class Program { internal const string VERSION = "v0.1.0"; - internal static IServiceProvider SERVICE_PROVIDER = null!; + internal static IServiceProvider? SERVICE_PROVIDER; [STAThread] private static void Main() From 5416e404fd8ebb59be5087c94ab7ae1dcd4b2a23 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 11 Jul 2022 18:04:16 +0200 Subject: [PATCH 04/43] Added another splitter container for the right side (top/down) --- .../UI WinForms/Components/Main.Designer.cs | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/Main.Designer.cs b/I18N Commander/UI WinForms/Components/Main.Designer.cs index 6e3aef1..44d557c 100644 --- a/I18N Commander/UI WinForms/Components/Main.Designer.cs +++ b/I18N Commander/UI WinForms/Components/Main.Designer.cs @@ -30,12 +30,16 @@ { this.tableLayout = new System.Windows.Forms.TableLayoutPanel(); this.flowLayoutBottom = new System.Windows.Forms.FlowLayoutPanel(); - this.splitContainer = new System.Windows.Forms.SplitContainer(); + this.splitContainerLR = new System.Windows.Forms.SplitContainer(); this.sectionTree = new UI_WinForms.Components.SectionTree(); + this.splitContainerRTB = new System.Windows.Forms.SplitContainer(); this.tableLayout.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); - this.splitContainer.Panel1.SuspendLayout(); - this.splitContainer.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainerLR)).BeginInit(); + this.splitContainerLR.Panel1.SuspendLayout(); + this.splitContainerLR.Panel2.SuspendLayout(); + this.splitContainerLR.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainerRTB)).BeginInit(); + this.splitContainerRTB.SuspendLayout(); this.SuspendLayout(); // // tableLayout @@ -44,7 +48,7 @@ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); this.tableLayout.Controls.Add(this.flowLayoutBottom, 0, 1); - this.tableLayout.Controls.Add(this.splitContainer, 0, 0); + this.tableLayout.Controls.Add(this.splitContainerLR, 0, 0); this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayout.Location = new System.Drawing.Point(0, 0); this.tableLayout.Name = "tableLayout"; @@ -63,21 +67,25 @@ this.flowLayoutBottom.Size = new System.Drawing.Size(965, 66); this.flowLayoutBottom.TabIndex = 0; // - // splitContainer + // splitContainerLR // - this.splitContainer.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; - this.splitContainer.Location = new System.Drawing.Point(3, 3); - this.splitContainer.Name = "splitContainer"; + this.splitContainerLR.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.splitContainerLR.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainerLR.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainerLR.Location = new System.Drawing.Point(3, 3); + this.splitContainerLR.Name = "splitContainerLR"; // - // splitContainer.Panel1 + // splitContainerLR.Panel1 // - this.splitContainer.Panel1.Controls.Add(this.sectionTree); - this.splitContainer.Panel1MinSize = 300; - this.splitContainer.Size = new System.Drawing.Size(959, 531); - this.splitContainer.SplitterDistance = 319; - this.splitContainer.TabIndex = 1; + this.splitContainerLR.Panel1.Controls.Add(this.sectionTree); + this.splitContainerLR.Panel1MinSize = 300; + // + // splitContainerLR.Panel2 + // + this.splitContainerLR.Panel2.Controls.Add(this.splitContainerRTB); + this.splitContainerLR.Size = new System.Drawing.Size(959, 531); + this.splitContainerLR.SplitterDistance = 319; + this.splitContainerLR.TabIndex = 1; // // sectionTree // @@ -88,6 +96,19 @@ this.sectionTree.Size = new System.Drawing.Size(317, 529); this.sectionTree.TabIndex = 0; // + // splitContainerRTB + // + this.splitContainerRTB.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.splitContainerRTB.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainerRTB.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainerRTB.Location = new System.Drawing.Point(0, 0); + this.splitContainerRTB.Name = "splitContainerRTB"; + this.splitContainerRTB.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.splitContainerRTB.Panel1MinSize = 190; + this.splitContainerRTB.Size = new System.Drawing.Size(636, 531); + this.splitContainerRTB.SplitterDistance = 211; + this.splitContainerRTB.TabIndex = 0; + // // Main // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); @@ -97,9 +118,12 @@ this.Name = "Main"; this.Size = new System.Drawing.Size(965, 603); this.tableLayout.ResumeLayout(false); - this.splitContainer.Panel1.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); - this.splitContainer.ResumeLayout(false); + this.splitContainerLR.Panel1.ResumeLayout(false); + this.splitContainerLR.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainerLR)).EndInit(); + this.splitContainerLR.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainerRTB)).EndInit(); + this.splitContainerRTB.ResumeLayout(false); this.ResumeLayout(false); } @@ -108,7 +132,8 @@ private TableLayoutPanel tableLayout; private FlowLayoutPanel flowLayoutBottom; - private SplitContainer splitContainer; + private SplitContainer splitContainerLR; + private SplitContainer splitContainerRTB; private SectionTree sectionTree; } } From 3bbff31fe0dda095ae259a02dcc6be32f3f9fe14 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 11 Jul 2022 19:52:15 +0200 Subject: [PATCH 05/43] Added basic text elements layout --- .../UI WinForms/Components/Main.Designer.cs | 19 +- .../Components/TextElements.Designer.cs | 181 ++++++++++++++++++ .../UI WinForms/Components/TextElements.cs | 9 + .../UI WinForms/Components/TextElements.resx | 63 ++++++ .../UI WinForms/Resources/Icons.Designer.cs | 20 ++ .../UI WinForms/Resources/Icons.resx | 6 + .../Resources/icons8-add-tag-512.png | Bin 0 -> 2840 bytes .../Resources/icons8-remove-tag-512.png | Bin 0 -> 2910 bytes 8 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 I18N Commander/UI WinForms/Components/TextElements.Designer.cs create mode 100644 I18N Commander/UI WinForms/Components/TextElements.cs create mode 100644 I18N Commander/UI WinForms/Components/TextElements.resx create mode 100644 I18N Commander/UI WinForms/Resources/icons8-add-tag-512.png create mode 100644 I18N Commander/UI WinForms/Resources/icons8-remove-tag-512.png diff --git a/I18N Commander/UI WinForms/Components/Main.Designer.cs b/I18N Commander/UI WinForms/Components/Main.Designer.cs index 44d557c..2293456 100644 --- a/I18N Commander/UI WinForms/Components/Main.Designer.cs +++ b/I18N Commander/UI WinForms/Components/Main.Designer.cs @@ -33,12 +33,14 @@ this.splitContainerLR = new System.Windows.Forms.SplitContainer(); this.sectionTree = new UI_WinForms.Components.SectionTree(); this.splitContainerRTB = new System.Windows.Forms.SplitContainer(); + this.textElements = new UI_WinForms.Components.TextElements(); this.tableLayout.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainerLR)).BeginInit(); this.splitContainerLR.Panel1.SuspendLayout(); this.splitContainerLR.Panel2.SuspendLayout(); this.splitContainerLR.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainerRTB)).BeginInit(); + this.splitContainerRTB.Panel1.SuspendLayout(); this.splitContainerRTB.SuspendLayout(); this.SuspendLayout(); // @@ -104,11 +106,24 @@ this.splitContainerRTB.Location = new System.Drawing.Point(0, 0); this.splitContainerRTB.Name = "splitContainerRTB"; this.splitContainerRTB.Orientation = System.Windows.Forms.Orientation.Horizontal; - this.splitContainerRTB.Panel1MinSize = 190; + // + // splitContainerRTB.Panel1 + // + this.splitContainerRTB.Panel1.Controls.Add(this.textElements); + this.splitContainerRTB.Panel1MinSize = 200; this.splitContainerRTB.Size = new System.Drawing.Size(636, 531); this.splitContainerRTB.SplitterDistance = 211; this.splitContainerRTB.TabIndex = 0; // + // textElements + // + this.textElements.Dock = System.Windows.Forms.DockStyle.Fill; + this.textElements.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.textElements.Location = new System.Drawing.Point(0, 0); + this.textElements.Name = "textElements"; + this.textElements.Size = new System.Drawing.Size(634, 209); + this.textElements.TabIndex = 0; + // // Main // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); @@ -122,6 +137,7 @@ this.splitContainerLR.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.splitContainerLR)).EndInit(); this.splitContainerLR.ResumeLayout(false); + this.splitContainerRTB.Panel1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.splitContainerRTB)).EndInit(); this.splitContainerRTB.ResumeLayout(false); this.ResumeLayout(false); @@ -135,5 +151,6 @@ private SplitContainer splitContainerLR; private SplitContainer splitContainerRTB; private SectionTree sectionTree; + private TextElements textElements; } } diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs new file mode 100644 index 0000000..2214e43 --- /dev/null +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -0,0 +1,181 @@ +namespace UI_WinForms.Components +{ + partial class TextElements + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.tableLayout = new System.Windows.Forms.TableLayoutPanel(); + this.flowLayoutToolbar = new System.Windows.Forms.FlowLayoutPanel(); + this.buttonAdd = new System.Windows.Forms.Button(); + this.buttonRemove = new System.Windows.Forms.Button(); + this.buttonRename = new System.Windows.Forms.Button(); + this.listTextElements = new System.Windows.Forms.ListBox(); + this.textBoxFilter = new System.Windows.Forms.TextBox(); + this.labelFilter = new System.Windows.Forms.Label(); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.tableLayout.SuspendLayout(); + this.flowLayoutToolbar.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayout + // + this.tableLayout.ColumnCount = 3; + this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 66F)); + this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); + this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayout.RowCount = 2; + this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableLayout.Controls.Add(this.flowLayoutToolbar, 0, 0); + this.tableLayout.Controls.Add(this.listTextElements, 1, 0); + this.tableLayout.Controls.Add(this.textBoxFilter, 2, 1); + this.tableLayout.Controls.Add(this.labelFilter, 1, 1); + this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayout.Location = new System.Drawing.Point(0, 0); + this.tableLayout.Name = "tableLayout"; + this.tableLayout.Size = new System.Drawing.Size(706, 201); + this.tableLayout.TabIndex = 0; + // + // flowLayoutToolbar + // + this.flowLayoutToolbar.Controls.Add(this.buttonAdd); + this.flowLayoutToolbar.Controls.Add(this.buttonRemove); + this.flowLayoutToolbar.Controls.Add(this.buttonRename); + this.flowLayoutToolbar.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutToolbar.FlowDirection = System.Windows.Forms.FlowDirection.BottomUp; + this.flowLayoutToolbar.Location = new System.Drawing.Point(0, 0); + this.flowLayoutToolbar.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutToolbar.Name = "flowLayoutToolbar"; + this.tableLayout.SetRowSpan(this.flowLayoutToolbar, 2); + this.flowLayoutToolbar.Size = new System.Drawing.Size(66, 201); + this.flowLayoutToolbar.TabIndex = 0; + // + // buttonAdd + // + this.buttonAdd.FlatAppearance.BorderSize = 0; + this.buttonAdd.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonAdd.Image = global::UI_WinForms.Resources.Icons.icons8_add_tag_512; + this.buttonAdd.Location = new System.Drawing.Point(3, 138); + this.buttonAdd.Name = "buttonAdd"; + this.buttonAdd.Size = new System.Drawing.Size(60, 60); + this.buttonAdd.TabIndex = 0; + this.toolTip.SetToolTip(this.buttonAdd, "Add text element to selected section"); + this.buttonAdd.UseVisualStyleBackColor = true; + // + // buttonRemove + // + this.buttonRemove.FlatAppearance.BorderSize = 0; + this.buttonRemove.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonRemove.Image = global::UI_WinForms.Resources.Icons.icons8_remove_tag_512; + this.buttonRemove.Location = new System.Drawing.Point(3, 72); + this.buttonRemove.Name = "buttonRemove"; + this.buttonRemove.Size = new System.Drawing.Size(60, 60); + this.buttonRemove.TabIndex = 2; + this.toolTip.SetToolTip(this.buttonRemove, "Delete this text element"); + this.buttonRemove.UseVisualStyleBackColor = true; + // + // buttonRename + // + this.buttonRename.FlatAppearance.BorderSize = 0; + this.buttonRename.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonRename.Image = global::UI_WinForms.Resources.Icons.icons8_rename_512; + this.buttonRename.Location = new System.Drawing.Point(3, 6); + this.buttonRename.Name = "buttonRename"; + this.buttonRename.Size = new System.Drawing.Size(60, 60); + this.buttonRename.TabIndex = 1; + this.toolTip.SetToolTip(this.buttonRename, "Rename this text element"); + this.buttonRename.UseVisualStyleBackColor = true; + // + // listTextElements + // + this.tableLayout.SetColumnSpan(this.listTextElements, 2); + this.listTextElements.Dock = System.Windows.Forms.DockStyle.Fill; + this.listTextElements.FormattingEnabled = true; + this.listTextElements.ItemHeight = 28; + this.listTextElements.Location = new System.Drawing.Point(69, 3); + this.listTextElements.Name = "listTextElements"; + this.listTextElements.ScrollAlwaysVisible = true; + this.listTextElements.Size = new System.Drawing.Size(634, 200); + this.listTextElements.TabIndex = 1; + // + // textBoxFilter + // + this.textBoxFilter.Dock = System.Windows.Forms.DockStyle.Fill; + this.textBoxFilter.Location = new System.Drawing.Point(149, 164); + this.textBoxFilter.Name = "textBoxFilter"; + this.textBoxFilter.Size = new System.Drawing.Size(554, 34); + this.textBoxFilter.TabIndex = 2; + this.textBoxFilter.WordWrap = false; + // + // labelFilter + // + this.labelFilter.AutoSize = true; + this.labelFilter.Dock = System.Windows.Forms.DockStyle.Fill; + this.labelFilter.Location = new System.Drawing.Point(69, 161); + this.labelFilter.Name = "labelFilter"; + this.labelFilter.Size = new System.Drawing.Size(74, 40); + this.labelFilter.TabIndex = 3; + this.labelFilter.Text = "Filter:"; + this.labelFilter.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // toolTip + // + this.toolTip.AutoPopDelay = 30000; + this.toolTip.InitialDelay = 500; + this.toolTip.ReshowDelay = 100; + this.toolTip.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info; + this.toolTip.ToolTipTitle = "Help"; + // + // TextElements + // + this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.Controls.Add(this.tableLayout); + this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.Name = "TextElements"; + this.Size = new System.Drawing.Size(706, 201); + this.tableLayout.ResumeLayout(false); + this.tableLayout.PerformLayout(); + this.flowLayoutToolbar.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private TableLayoutPanel tableLayout; + private FlowLayoutPanel flowLayoutToolbar; + private Button buttonAdd; + private Button buttonRename; + private Button buttonRemove; + private ToolTip toolTip; + private ListBox listTextElements; + private TextBox textBoxFilter; + private Label labelFilter; + } +} diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs new file mode 100644 index 0000000..3da6153 --- /dev/null +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -0,0 +1,9 @@ +namespace UI_WinForms.Components; + +public partial class TextElements : UserControl +{ + public TextElements() + { + this.InitializeComponent(); + } +} \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.resx b/I18N Commander/UI WinForms/Components/TextElements.resx new file mode 100644 index 0000000..99de901 --- /dev/null +++ b/I18N Commander/UI WinForms/Components/TextElements.resx @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs index a97cc64..42848c3 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs +++ b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs @@ -70,6 +70,16 @@ namespace UI_WinForms.Resources { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_add_tag_512 { + get { + object obj = ResourceManager.GetObject("icons8_add_tag_512", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -160,6 +170,16 @@ namespace UI_WinForms.Resources { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_remove_tag_512 { + get { + object obj = ResourceManager.GetObject("icons8_remove_tag_512", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/I18N Commander/UI WinForms/Resources/Icons.resx b/I18N Commander/UI WinForms/Resources/Icons.resx index 0894a59..dc4a41d 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.resx +++ b/I18N Commander/UI WinForms/Resources/Icons.resx @@ -121,6 +121,9 @@ icons8-add-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + icons8-add-tag-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + icons8-browse-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -148,6 +151,9 @@ icons8-open-file-under-cursor-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + icons8-remove-tag-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + icons8-rename-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/I18N Commander/UI WinForms/Resources/icons8-add-tag-512.png b/I18N Commander/UI WinForms/Resources/icons8-add-tag-512.png new file mode 100644 index 0000000000000000000000000000000000000000..1737c75a293f5aad86c5da687dcd1db1058b19ac GIT binary patch literal 2840 zcmai0d0bLi8^+W!&Ctv=$CNNDH9&AFNXi8h7t93{u~L=`QtVs>Eh|ei?Q35ww2YtH zrnxq0F6A~YwyEWw?Tc2lIaB)S)LgW&X{zu0;{xYA&-=XZd6#oZ3kvix)VI)w!C;2I zWKsz9jnX`N6QNIn#UmvQrc=zL(m)y|fWQ(7&+Kn6k~;0t90 z1ra%_OMrfBwlPS=C#PqiV88u!EU_tyyE~NXfv7# zO{h{9s2SUdsiLclF@ zaKsTD9Y>W%+0ZPo86e~T@iiTdaDyx;6atwi14YvK(XFvI4+SJ+tFa}YH`?C_#AuZX z43;MAM5I_MV#l%o_Na46ZVZ))IG~&%1w6P=fr&^D4u=QTCms=`>0AUBjdetiRd?J% z9s@vXKR+AhOJo?$#Qhzb8|J&<-9`!j7*WV>Y!4cJ=nlb*Un0uo(+SR3cLR zR&Q*{#io2+-Ih!F%@^Gx>*z+)y^Z`!R-A2lo0Xw6t?@c;@%>M6+|DDq{s`5aKPo-Q z$8kzg)611B>2dU^5@yZ;&cw~>g*S)hX;XhKnzZJ+qJ!Ihdc*6evJ{)j2`+~9tn|}B z@`)5~htuh+RrP8=H}e@w%;I=bkB93*;if}3p3wGe3}~NlK~j8HZ!yc%@^rLqp>4|3 zr{Fx3h&<4G!1e=qO&zAhzuIJ$b*MsuYtVVDFlSOZ@WhJ#4AZdplMIFia&5!+MA)WY zvcNuKi_??`UKpnZx7gsOJ-PnrvpU~uR-9RWPfgHgrnxmSx;JU$9AmxpFUvmZtjT_8 zA9i!Ob={Q{eLcXLVI#{-v1C=d=Q{Cu-C4`m1R2c8rAz#s_1|!t&*})un=3+IMewq3 z`{?nng!k5d()ztA{_+b9*OdEgEhn1PAr(Sj+n%%QhSk}t`FN=s26 zS`}j%HvRHOd7Nt*MR_OpxRO@js^vg^j||CQbkIK0Zd+LJTfmfldoU?4tPXB;v46|S zHfjztYK~vjdWYd+c-il&TsR8b<_@NvIxb*V{&t8zx2N}I!>huo#bs4aWhVNz8#`0i z-MT#TQT!VJd^PPi`$EjN10dQ^z2R7IPFL82a8>5Mw&^R9&pMwqdTaR>du&LeHKJ3E z1P4C)sk+T;PWMf^*I>;k-aMo?W9V{La*~Gy-MrIaz43nk2d=8j(7a4P_nQWt2GuVc zggOdSbDe{JA9TgsJSn1owjl+kUHg2=b&NJ#zc(ObusaG_E}3HAI-CSg?EVytSV`+} zia7*%Hrv$Tp1<5IGEYTRer%iI7JRE#oIJ3&r^HKc*Ml6x^xb@_S^v}% zEu@{rSHhHsYO@?#Yj5k&+5=AQrggeARr^ZvVy{1MTH0epKaP>=)&K_uH)Oi+?HSPz%WGcozEPVqQDOKJF<4(pB`*NmZ#*Q>;BNhM#09rK7jA^zgYi>q4FhH}`Mgz< zSs|V$)9E9V-+j(D-iAAjY?`m`isxsjv%}TfJQwcr^STDJh8gamYezyw^O+~XYxfpi){xemPRq6PZ zY1=I9_8rN@ho34vtl#R=Q_*~*p!r5oNoCF%r?4$ZOL#_9XUHA38!`EhOCRjeb8B|I zV0IA>`_0HFsS-$c^k4>uz4xAC2Y)OY>i3hI9Qftv&T{9oS_PSM%A?QEs7HqetU|ix zW(I^&C(5nujyK+NVp%SdoK)stRXB65IV|r$sWP|JUc2LKN_o<^edW7Hl@>b}3;uiF zW`jX>g#YXX(mqsKU}&b<%3OWkt#XMfbJ}2t1%0Z*x}wEb6gglCFRE!w8!9Y4nz0Z6 zpR1%2zeP_RX@U`l2L(2higLHFK67|D;=%l|*E{@ijVgmgO@UWgU9c^=_v`(19~N(( z?)_U#+ox~6vm!UxBTN5;;Hd>C&)Xd~OOmfK-n!}D31~7>V&C=dzpuQcjM|za+2mzD)fxr{1;rcZoL2i literal 0 HcmV?d00001 diff --git a/I18N Commander/UI WinForms/Resources/icons8-remove-tag-512.png b/I18N Commander/UI WinForms/Resources/icons8-remove-tag-512.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd6ee65979ce623651a1ea706c8860ab975febf GIT binary patch literal 2910 zcmai0dmxi-8=ue{AygwOGoM2aJ0{t;r5szC%Ok*zMds}dbnw-8>quzFirAW zXD{g6M)Ay<4Sf;}o@T*dDrZ0+s+dY~C(!v^6pg_T1yB;M0K#D~qJu<0qq6}qA`}P% zc_idu^(7<%WRQ^lwiFCS-~_P1wb4SrJKDpC9?hoPGms9e)QJ)TB)|p4G=zl9;fV+m z5^_?P0R2{MqmhV7ikMA8QYoGYC%zCs*rIGv800E-1X0Lf61<#UzZyd;5|Skr3kYa* zWMm{N5|82w!_ZiJdwVnnhsNP-Acl=7iYKN?YvS@VjBL= z5K7;VO(ZnIkH{3t|E6=f-yB0Ec8P!z@inR6#Pp{Fkx!HWKzjiqez=ehxI_RvvB_j6 z6nP<#xwJ6A9pHtDStR6g+rI(+Ami`*3L#Foq7uG55OR&1F`>?2B%~b{>ujx7vCNT=10YUi5FlHdKlar^A z&jdLTCh~G~M37yaR$%Q{tgykMrevU^5g=QT8Rbk9Lo6Hy>P?KD4HoNzwI^Wh2+LPm zVQ>TtW>R^Q4c!F>O-%cLd__kih>!(^LRbrm#C&1YUn4^+Zrq=t5z*fTPn;zD<%&XfQ+v?Whh_+RdWb-a=`jTGpxGmYhDoWXh8L7@jM*V6ol$5se(z&{4Y7YAF3i@~oIDj*E%$(0z5QS`d?3gU@Rb_Ab^Y(Dbs$H)FPF;$>{@Jl*=NtQ) zgubHf?W&x?)zzOmthXy|fQ3B_O}}Ody)D!PYXd|u*xdPwM+ugdqYu4Zn#s;SAh4pNz@l$A+_IJxRF)LT ztuUak;hyv4%T*6Zx*56ai~!${eWJ zIUZ0x+{NZ+US2#Nq+L!f-82f9x6Zyyl%EpxzTKi#jto2>VbG~jqtUt5B6dSubAm~X zY}4|rbpM6QbrDb1W*@s*edp<5N}#*O^$JRe$&jnpq1CB^UaAb6p&g}T7#9|L1&B3| zeO%|B91QF5CzyLJyv1_gL2Ui4TP|ci*_W7zTf(`(WLJT0rWXn#S2er9H9j6`hM$xF z7SQHphsx|v3qal`+p$k&^jDQ%DM8}y)f0)UTNe$hSRQYfC|9)w*Qk31>)tih`E?%X zyT~~1(korf59y4c8z+ZSBIHZ@uNbkNZXFr~?!J(|If}g4x4fwVA7T+p4#^hure=uq zq6jOA9x#8iJpW_12ag9kJ?V(BXw&b}O!$vPciU%`%yHOIihyC<3)hJjJ>3;kDmwg5 zr8^Jx#oK1jSaQ@aU2b^e`h#Cg<-~WgCKbKBy5pIZ`knR_oaBYib>TKwVRTwYlYGg* z9n*~kT(Hq#yN1Zuq4Q-~N`A${*Y&r$atY>cupM=+7H>SGl7fJLogeo5(0=mn>zET% zD0u5)+WS1En+vV#G1@k#dtHZ(e}|WR+^ugFZJh1W2|quwXP@yArDCPZv{h$`@g{Du z=5bfEic~W;JW0(k%6*mU9io5GgI@YLHY&`BIYF?Y&N*ZKCB!b(f&`Vc$Kh80YJ< z(2rJ$xPV66S!%zp*7-HyfQ?W7$KPr$*7?afq}wvoqO-~O{Yt`jXYLs3*BGrGFYuzW z@6(4LmkrPs7cXx!4P5D^=BAZr&6r8V7n4d*U}~S~^P|Z)%1eQ;G@g9;gdy>%eWu z$A9q|Wtj!tS|%L4L E0AvQBivR!s literal 0 HcmV?d00001 From 538dbe1707cbbeb656cef7159595bd47b601cbfa Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 20:02:47 +0200 Subject: [PATCH 06/43] Fixed wrong layout caused by listbox's integral height setting --- .../UI WinForms/Components/TextElements.Designer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index 2214e43..0e614cb 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -48,9 +48,6 @@ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 66F)); this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayout.RowCount = 2; - this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); this.tableLayout.Controls.Add(this.flowLayoutToolbar, 0, 0); this.tableLayout.Controls.Add(this.listTextElements, 1, 0); this.tableLayout.Controls.Add(this.textBoxFilter, 2, 1); @@ -58,6 +55,9 @@ this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayout.Location = new System.Drawing.Point(0, 0); this.tableLayout.Name = "tableLayout"; + this.tableLayout.RowCount = 2; + this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); this.tableLayout.Size = new System.Drawing.Size(706, 201); this.tableLayout.TabIndex = 0; // @@ -116,11 +116,12 @@ this.tableLayout.SetColumnSpan(this.listTextElements, 2); this.listTextElements.Dock = System.Windows.Forms.DockStyle.Fill; this.listTextElements.FormattingEnabled = true; + this.listTextElements.IntegralHeight = false; this.listTextElements.ItemHeight = 28; this.listTextElements.Location = new System.Drawing.Point(69, 3); this.listTextElements.Name = "listTextElements"; this.listTextElements.ScrollAlwaysVisible = true; - this.listTextElements.Size = new System.Drawing.Size(634, 200); + this.listTextElements.Size = new System.Drawing.Size(634, 155); this.listTextElements.TabIndex = 1; // // textBoxFilter From 4767aae21758bc0bf5a68846d66f410d52240ade Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 20:20:17 +0200 Subject: [PATCH 07/43] Disable the text element toolbar by default --- I18N Commander/UI WinForms/Components/TextElements.Designer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index 0e614cb..bac394c 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -77,6 +77,7 @@ // // buttonAdd // + this.buttonAdd.Enabled = false; this.buttonAdd.FlatAppearance.BorderSize = 0; this.buttonAdd.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.buttonAdd.Image = global::UI_WinForms.Resources.Icons.icons8_add_tag_512; @@ -89,6 +90,7 @@ // // buttonRemove // + this.buttonRemove.Enabled = false; this.buttonRemove.FlatAppearance.BorderSize = 0; this.buttonRemove.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.buttonRemove.Image = global::UI_WinForms.Resources.Icons.icons8_remove_tag_512; @@ -101,6 +103,7 @@ // // buttonRename // + this.buttonRename.Enabled = false; this.buttonRename.FlatAppearance.BorderSize = 0; this.buttonRename.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.buttonRename.Image = global::UI_WinForms.Resources.Icons.icons8_rename_512; From aca986b18d66ae4a353e1c331f607e6690c0301a Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 20:49:31 +0200 Subject: [PATCH 08/43] Added method to load single section --- I18N Commander/Processor/SectionProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index a008a78..7be9030 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -135,4 +135,6 @@ public static class SectionProcessor await db.SaveChangesAsync(); return section; } + + public static Section GetSection(DataContext db, string sectionKey) => db.Sections.First(n => n.DataKey == sectionKey); } \ No newline at end of file From efb6732cd716d1fba0b91a7921e60a5ae3a045a3 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 20:50:03 +0200 Subject: [PATCH 09/43] Defined AppEvents by implemented section changed event --- I18N Commander/UI WinForms/AppEvents.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 I18N Commander/UI WinForms/AppEvents.cs diff --git a/I18N Commander/UI WinForms/AppEvents.cs b/I18N Commander/UI WinForms/AppEvents.cs new file mode 100644 index 0000000..41191ae --- /dev/null +++ b/I18N Commander/UI WinForms/AppEvents.cs @@ -0,0 +1,10 @@ +using DataModel.Database; + +namespace UI_WinForms; + +internal static class AppEvents +{ + internal static event EventHandler
WhenSectionChanged; + + internal static void SectionChanged(Section section) => AppEvents.WhenSectionChanged?.Invoke(null, section); +} \ No newline at end of file From 0193c8de0a4ccbbb6dd809fb404e61eda81ecac2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 20:50:25 +0200 Subject: [PATCH 10/43] Fire the section changed event --- I18N Commander/UI WinForms/Components/SectionTree.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs index 02ede17..a436b59 100644 --- a/I18N Commander/UI WinForms/Components/SectionTree.cs +++ b/I18N Commander/UI WinForms/Components/SectionTree.cs @@ -196,6 +196,14 @@ public partial class SectionTree : UserControl // 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 = SectionProcessor.GetSection(this.db, selectedNode.Name); + AppEvents.SectionChanged(section); + } } private async void buttonRename_Click(object sender, EventArgs e) From 10952769aa1b2f4e12cbb817ab60180f445163f1 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 20:50:42 +0200 Subject: [PATCH 11/43] Manage the current selected section --- .../UI WinForms/Components/TextElements.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 3da6153..694aec8 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -1,9 +1,20 @@ -namespace UI_WinForms.Components; +using DataModel.Database; + +namespace UI_WinForms.Components; public partial class TextElements : UserControl { + private Section? currentSection; + public TextElements() { this.InitializeComponent(); + + // When the section is changed, update this component: + AppEvents.WhenSectionChanged += (sender, section) => + { + this.currentSection = section; + this.buttonAdd.Enabled = this.currentSection is not null; + }; } } \ No newline at end of file From 5428cb948925d87aa581f8bfa1f7394ae2af905d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 21:04:33 +0200 Subject: [PATCH 12/43] Implemented method to calculate a section's path --- I18N Commander/Processor/SectionProcessor.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 7be9030..31be354 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -137,4 +137,17 @@ public static class SectionProcessor } public static Section GetSection(DataContext db, string sectionKey) => db.Sections.First(n => n.DataKey == sectionKey); + + public static string GetSectionPath(DataContext db, string sectionKey) + { + var section = db.Sections.First(n => n.DataKey == sectionKey); + var path = section.Name; + while (section.Parent != null) + { + section = section.Parent; + path = $"{section.Name}/{path}"; + } + + return $"Section's path: {path}"; + } } \ No newline at end of file From 620bc992a82b193312176d631f6e748062625376 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 Jul 2022 21:05:50 +0200 Subject: [PATCH 13/43] Implemented the current section's path - Added label for path - Increased the min. height for the component --- .../UI WinForms/Components/Main.Designer.cs | 6 ++-- .../Components/TextElements.Designer.cs | 31 ++++++++++++++----- .../UI WinForms/Components/TextElements.cs | 19 ++++++++++++ 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/Main.Designer.cs b/I18N Commander/UI WinForms/Components/Main.Designer.cs index 2293456..efcf16a 100644 --- a/I18N Commander/UI WinForms/Components/Main.Designer.cs +++ b/I18N Commander/UI WinForms/Components/Main.Designer.cs @@ -110,9 +110,9 @@ // splitContainerRTB.Panel1 // this.splitContainerRTB.Panel1.Controls.Add(this.textElements); - this.splitContainerRTB.Panel1MinSize = 200; + this.splitContainerRTB.Panel1MinSize = 240; this.splitContainerRTB.Size = new System.Drawing.Size(636, 531); - this.splitContainerRTB.SplitterDistance = 211; + this.splitContainerRTB.SplitterDistance = 240; this.splitContainerRTB.TabIndex = 0; // // textElements @@ -121,7 +121,7 @@ this.textElements.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.textElements.Location = new System.Drawing.Point(0, 0); this.textElements.Name = "textElements"; - this.textElements.Size = new System.Drawing.Size(634, 209); + this.textElements.Size = new System.Drawing.Size(634, 238); this.textElements.TabIndex = 0; // // Main diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index bac394c..2369d9d 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -38,6 +38,7 @@ this.textBoxFilter = new System.Windows.Forms.TextBox(); this.labelFilter = new System.Windows.Forms.Label(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.labelSectionPath = new System.Windows.Forms.Label(); this.tableLayout.SuspendLayout(); this.flowLayoutToolbar.SuspendLayout(); this.SuspendLayout(); @@ -49,13 +50,15 @@ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayout.Controls.Add(this.flowLayoutToolbar, 0, 0); - this.tableLayout.Controls.Add(this.listTextElements, 1, 0); - this.tableLayout.Controls.Add(this.textBoxFilter, 2, 1); - this.tableLayout.Controls.Add(this.labelFilter, 1, 1); + this.tableLayout.Controls.Add(this.listTextElements, 1, 1); + this.tableLayout.Controls.Add(this.textBoxFilter, 2, 2); + this.tableLayout.Controls.Add(this.labelFilter, 1, 2); + this.tableLayout.Controls.Add(this.labelSectionPath, 1, 0); this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayout.Location = new System.Drawing.Point(0, 0); this.tableLayout.Name = "tableLayout"; - this.tableLayout.RowCount = 2; + this.tableLayout.RowCount = 3; + this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); this.tableLayout.Size = new System.Drawing.Size(706, 201); @@ -71,7 +74,7 @@ this.flowLayoutToolbar.Location = new System.Drawing.Point(0, 0); this.flowLayoutToolbar.Margin = new System.Windows.Forms.Padding(0); this.flowLayoutToolbar.Name = "flowLayoutToolbar"; - this.tableLayout.SetRowSpan(this.flowLayoutToolbar, 2); + this.tableLayout.SetRowSpan(this.flowLayoutToolbar, 3); this.flowLayoutToolbar.Size = new System.Drawing.Size(66, 201); this.flowLayoutToolbar.TabIndex = 0; // @@ -121,10 +124,10 @@ this.listTextElements.FormattingEnabled = true; this.listTextElements.IntegralHeight = false; this.listTextElements.ItemHeight = 28; - this.listTextElements.Location = new System.Drawing.Point(69, 3); + this.listTextElements.Location = new System.Drawing.Point(69, 43); this.listTextElements.Name = "listTextElements"; this.listTextElements.ScrollAlwaysVisible = true; - this.listTextElements.Size = new System.Drawing.Size(634, 155); + this.listTextElements.Size = new System.Drawing.Size(634, 115); this.listTextElements.TabIndex = 1; // // textBoxFilter @@ -155,6 +158,19 @@ this.toolTip.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info; this.toolTip.ToolTipTitle = "Help"; // + // labelSectionPath + // + this.labelSectionPath.AutoSize = true; + this.tableLayout.SetColumnSpan(this.labelSectionPath, 2); + this.labelSectionPath.Dock = System.Windows.Forms.DockStyle.Fill; + this.labelSectionPath.Location = new System.Drawing.Point(69, 0); + this.labelSectionPath.Name = "labelSectionPath"; + this.labelSectionPath.Size = new System.Drawing.Size(634, 40); + this.labelSectionPath.TabIndex = 4; + this.labelSectionPath.Text = "Path"; + this.labelSectionPath.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.toolTip.SetToolTip(this.labelSectionPath, "The path of the currently selected section"); + // // TextElements // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); @@ -181,5 +197,6 @@ private ListBox listTextElements; private TextBox textBoxFilter; private Label labelFilter; + private Label labelSectionPath; } } diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 694aec8..1b044e8 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -1,20 +1,39 @@ using DataModel.Database; +using DataModel.Database.Common; +using Microsoft.Extensions.DependencyInjection; +using Processor; namespace UI_WinForms.Components; public partial class TextElements : UserControl { + private readonly DataContext db; + private Section? currentSection; public TextElements() { this.InitializeComponent(); + // Check if we are in the designer: + if(Program.SERVICE_PROVIDER is null) + return; + + // Get the DI context from the main form: + this.db = Program.SERVICE_PROVIDER.GetService()!; + + // Dispose of the context when the control is disposed: + this.Disposed += (_, _) => this.db.Dispose(); + // When the section is changed, update this component: AppEvents.WhenSectionChanged += (sender, section) => { this.currentSection = section; this.buttonAdd.Enabled = this.currentSection is not null; + + // Update the path: + if (this.currentSection is not null) + this.labelSectionPath.Text = SectionProcessor.GetSectionPath(this.db, this.currentSection.DataKey); }; } } \ No newline at end of file From 6b49b3f3e73d10ed7b8c1bd74a2a433dc6bf29df Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 13 Jul 2022 19:31:59 +0200 Subject: [PATCH 14/43] Fixed selected section is off by one Closes #26 --- I18N Commander/UI WinForms/Components/SectionTree.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs index a436b59..2ebbc55 100644 --- a/I18N Commander/UI WinForms/Components/SectionTree.cs +++ b/I18N Commander/UI WinForms/Components/SectionTree.cs @@ -192,7 +192,7 @@ public partial class SectionTree : UserControl return; // Get the currently selected section: - var selectedNode = this.treeView.SelectedNode; + 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; From 2b5de0aa1ea8bc42edf78201bc4154f404478942 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 13 Jul 2022 20:04:26 +0200 Subject: [PATCH 15/43] Auto included must navigation properties in data model --- I18N Commander/DataModel/Database/Common/DataContext.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/I18N Commander/DataModel/Database/Common/DataContext.cs b/I18N Commander/DataModel/Database/Common/DataContext.cs index eee9081..f7b0b7c 100644 --- a/I18N Commander/DataModel/Database/Common/DataContext.cs +++ b/I18N Commander/DataModel/Database/Common/DataContext.cs @@ -37,6 +37,8 @@ public sealed class DataContext : DbContext modelBuilder.Entity
().HasIndex(n => n.Name); modelBuilder.Entity
().HasIndex(n => n.Depth); modelBuilder.Entity
().HasIndex(n => n.DataKey); + // modelBuilder.Entity
().Navigation(n => n.Parent).AutoInclude(); // Cyclic reference, does not work, though. + modelBuilder.Entity
().Navigation(n => n.TextElements).AutoInclude(); #endregion @@ -45,6 +47,7 @@ public sealed class DataContext : DbContext modelBuilder.Entity().HasIndex(n => n.Id); modelBuilder.Entity().HasIndex(n => n.Code); modelBuilder.Entity().HasIndex(n => n.Name); + modelBuilder.Entity().Navigation(n => n.Section).AutoInclude(); #endregion @@ -53,6 +56,7 @@ public sealed class DataContext : DbContext modelBuilder.Entity().HasIndex(n => n.Id); modelBuilder.Entity().HasIndex(n => n.Culture); modelBuilder.Entity().HasIndex(n => n.Text); + modelBuilder.Entity().Navigation(n => n.TextElement).AutoInclude(); #endregion } From 0adc95b0161c932bd0aaae5e50839c6d383edc85 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 13 Jul 2022 20:07:06 +0200 Subject: [PATCH 16/43] Fixed path construction of sections - Fixed missed loading of parents - Made processing async --- I18N Commander/Processor/SectionProcessor.cs | 14 +++++++++++--- .../UI WinForms/Components/TextElements.cs | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 31be354..560a712 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -138,13 +138,21 @@ public static class SectionProcessor public static Section GetSection(DataContext db, string sectionKey) => db.Sections.First(n => n.DataKey == sectionKey); - public static string GetSectionPath(DataContext db, string sectionKey) + public static async Task GetSectionPath(DataContext db, string sectionKey) { - var section = db.Sections.First(n => n.DataKey == sectionKey); + var section = await db.Sections.FirstAsync(n => n.DataKey == sectionKey); + + // Ensure, that the database loaded the section's parent: + await db.Entry(section).Reference(n => n.Parent).LoadAsync(); + var path = section.Name; while (section.Parent != null) { - section = section.Parent; + section = await db.Sections.FirstAsync(n => n.DataKey == section.Parent.DataKey); + + // Ensure, that the database loaded the section's parent: + await db.Entry(section).Reference(n => n.Parent).LoadAsync(); + path = $"{section.Name}/{path}"; } diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 1b044e8..3e244dd 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -26,14 +26,14 @@ public partial class TextElements : UserControl this.Disposed += (_, _) => this.db.Dispose(); // When the section is changed, update this component: - AppEvents.WhenSectionChanged += (sender, section) => + AppEvents.WhenSectionChanged += async (sender, section) => { this.currentSection = section; this.buttonAdd.Enabled = this.currentSection is not null; // Update the path: if (this.currentSection is not null) - this.labelSectionPath.Text = SectionProcessor.GetSectionPath(this.db, this.currentSection.DataKey); + this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.db, this.currentSection.DataKey); }; } } \ No newline at end of file From 52b0d3af0cd77df5d4625b7b1259991b749c4c95 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 16 Jul 2022 22:28:27 +0200 Subject: [PATCH 17/43] Changed assembly name to app name --- I18N Commander/UI WinForms/UI WinForms.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/I18N Commander/UI WinForms/UI WinForms.csproj b/I18N Commander/UI WinForms/UI WinForms.csproj index dc5c59a..390c5ab 100644 --- a/I18N Commander/UI WinForms/UI WinForms.csproj +++ b/I18N Commander/UI WinForms/UI WinForms.csproj @@ -8,6 +8,7 @@ true enable default + I18N Commander From fb2f573de41fd8d4bda59dfb08dc80f8a564178b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 16 Jul 2022 22:28:40 +0200 Subject: [PATCH 18/43] Spelling --- I18N Commander/DataModel/Database/Common/DataContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I18N Commander/DataModel/Database/Common/DataContext.cs b/I18N Commander/DataModel/Database/Common/DataContext.cs index f7b0b7c..4d633ee 100644 --- a/I18N Commander/DataModel/Database/Common/DataContext.cs +++ b/I18N Commander/DataModel/Database/Common/DataContext.cs @@ -37,7 +37,7 @@ public sealed class DataContext : DbContext modelBuilder.Entity
().HasIndex(n => n.Name); modelBuilder.Entity
().HasIndex(n => n.Depth); modelBuilder.Entity
().HasIndex(n => n.DataKey); - // modelBuilder.Entity
().Navigation(n => n.Parent).AutoInclude(); // Cyclic reference, does not work, though. + // modelBuilder.Entity
().Navigation(n => n.Parent).AutoInclude(); // Cycle-reference, does not work, though. modelBuilder.Entity
().Navigation(n => n.TextElements).AutoInclude(); #endregion From 32ebd691943c22d071985e16b03894f6215abb52 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 16 Jul 2022 22:40:06 +0200 Subject: [PATCH 19/43] Added loading of section's text entries --- .../Processor/TextElementProcessor.cs | 14 +++++++++ .../UI WinForms/Components/TextElements.cs | 29 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 I18N Commander/Processor/TextElementProcessor.cs diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs new file mode 100644 index 0000000..e1578dc --- /dev/null +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -0,0 +1,14 @@ +using System.Collections; +using DataModel.Database; +using DataModel.Database.Common; +using Microsoft.EntityFrameworkCore; + +namespace Processor; + +public static class TextElementProcessor +{ + public static Task> GetTextElements(DataContext db, Section section) + { + return Task.FromResult(db.TextElements.Where(n => n.Section == section).AsAsyncEnumerable()); + } +} \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 3e244dd..a371e2c 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -32,8 +32,33 @@ public partial class TextElements : UserControl this.buttonAdd.Enabled = this.currentSection is not null; // Update the path: - if (this.currentSection is not null) - this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.db, this.currentSection.DataKey); + if (this.currentSection is null) + return; + + this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.db, this.currentSection.DataKey); + this.LoadTextElements(); }; } + + // Loads all the text elements for the current section. + private async void LoadTextElements() + { + if (this.currentSection is null) + return; + + // Load the text elements: + var textElements = await TextElementProcessor.GetTextElements(this.db, this.currentSection); + + // Update the list: + this.listTextElements.Items.Clear(); + await foreach (var textElement in textElements) + { + var item = new ListViewItem(textElement.Name) + { + Tag = textElement + }; + + this.listTextElements.Items.Add(item); + } + } } \ No newline at end of file From 925f777d367ec5f84eaed4a856f05d301821ece8 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:32:43 +0200 Subject: [PATCH 20/43] Added automatic error handling to show a message box --- I18N Commander/UI WinForms/ExtensionsError.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 I18N Commander/UI WinForms/ExtensionsError.cs diff --git a/I18N Commander/UI WinForms/ExtensionsError.cs b/I18N Commander/UI WinForms/ExtensionsError.cs new file mode 100644 index 0000000..0873d09 --- /dev/null +++ b/I18N Commander/UI WinForms/ExtensionsError.cs @@ -0,0 +1,15 @@ +using Processor; + +namespace UI_WinForms; + +public static class ExtensionsError +{ + public static ProcessorResult ProcessError(this ProcessorResult result) + { + if (result.Successful) + return result; + + MessageBox.Show(result.ErrorMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return result; + } +} \ No newline at end of file From 0c4d4c03dc269c3dd5e0efa7c8f62e7f3fb2725e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:34:07 +0200 Subject: [PATCH 21/43] Changed to perform an async call --- I18N Commander/Processor/SectionProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 560a712..2242d94 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -78,7 +78,7 @@ public static class SectionProcessor Depth = parent.Depth + 1, }; - db.Sections.Add(section); + await db.Sections.AddAsync(section); await db.SaveChangesAsync(); return section; } From b233268d169ebd04e543defa97ddd0d3194a980c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:34:42 +0200 Subject: [PATCH 22/43] Added to determine which processes block a file --- .../Processor/GetBlockingProcesses.cs | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 I18N Commander/Processor/GetBlockingProcesses.cs diff --git a/I18N Commander/Processor/GetBlockingProcesses.cs b/I18N Commander/Processor/GetBlockingProcesses.cs new file mode 100644 index 0000000..d4a29a9 --- /dev/null +++ b/I18N Commander/Processor/GetBlockingProcesses.cs @@ -0,0 +1,135 @@ +namespace Processor; + +// +// Source: https://stackoverflow.com/a/20623311/2258393 +// + +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +public static class SystemUtil +{ + [StructLayout(LayoutKind.Sequential)] + private struct RM_UNIQUE_PROCESS + { + public readonly int dwProcessId; + private readonly System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; + } + + private const int RM_REBOOT_REASON_NONE = 0; + private const int CCH_RM_MAX_APP_NAME = 255; + private const int CCH_RM_MAX_SVC_NAME = 63; + + private enum RM_APP_TYPE + { + RM_UNKNOWN_APP = 0, + RM_MAIN_WINDOW = 1, + RM_OTHER_WINDOW = 2, + RM_SERVICE = 3, + RM_EXPLORER = 4, + RM_CONSOLE = 5, + RM_CRITICAL = 1000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct RM_PROCESS_INFO + { + public readonly RM_UNIQUE_PROCESS Process; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] + private readonly string strAppName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] + private readonly string strServiceShortName; + + private readonly RM_APP_TYPE ApplicationType; + private readonly uint AppStatus; + private readonly uint TSSessionId; + + [MarshalAs(UnmanagedType.Bool)] + private readonly bool bRestartable; + } + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + static extern int RmRegisterResources(uint pSessionHandle, UInt32 nFiles, string[] rgsFilenames, UInt32 nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, UInt32 nServices, string[] rgsServiceNames); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] + static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); + + [DllImport("rstrtmgr.dll")] + static extern int RmEndSession(uint pSessionHandle); + + [DllImport("rstrtmgr.dll")] + static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons); + + /// + /// Find out what process(es) have a lock on the specified file. + /// + /// Path of the file. + /// Processes locking the file + /// See also: + /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx + /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) + /// + public static List WhoIsLocking(string path) + { + var key = Guid.NewGuid().ToString(); + var processes = new List(); + + var res = RmStartSession(out var handle, 0, key); + if (res != 0) + return processes; + + try + { + const int ERROR_MORE_DATA = 234; + uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = RM_REBOOT_REASON_NONE; + var resources = new[] { path }; // Just checking on one resource. + res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null!, 0, null!); + + if (res != 0) + return processes; + + //Note: there's a race condition here -- the first call to RmGetList() returns + // the total number of process. However, when we call RmGetList() again to get + // the actual processes this number may have increased. + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null!, ref lpdwRebootReasons); + if (res == ERROR_MORE_DATA) + { + // Create an array to store the process results + var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; + + // Get the list + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + if (res == 0) + { + // Enumerate all of the results and add them to the list to be returned + for (var i = 0; i < pnProcInfo; i++) + { + try + { + processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); + } + // catch the error -- in case the process is no longer running + catch (ArgumentException) + { + } + } + } + else + return processes; + } + else if (res != 0) + return processes; + } + finally + { + RmEndSession(handle); + } + + return processes; + } +} \ No newline at end of file From 22778fbcf653fae17df35d9b1e09e593edb70cfb Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:35:09 +0200 Subject: [PATCH 23/43] Defined a processor result to handle error states --- I18N Commander/Processor/ProcessorResult.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 I18N Commander/Processor/ProcessorResult.cs diff --git a/I18N Commander/Processor/ProcessorResult.cs b/I18N Commander/Processor/ProcessorResult.cs new file mode 100644 index 0000000..5678787 --- /dev/null +++ b/I18N Commander/Processor/ProcessorResult.cs @@ -0,0 +1,3 @@ +namespace Processor; + +public readonly record struct ProcessorResult(TResult? Result, bool Successful = true, string ErrorMessage = ""); \ No newline at end of file From 3ef7bf1784cc8cb012139ae8111a5d9627d1c443 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:54:37 +0200 Subject: [PATCH 24/43] Keep track of the data file's path --- I18N Commander/DataModel/Setup.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/I18N Commander/DataModel/Setup.cs b/I18N Commander/DataModel/Setup.cs index ed1388d..2b87b24 100644 --- a/I18N Commander/DataModel/Setup.cs +++ b/I18N Commander/DataModel/Setup.cs @@ -11,6 +11,10 @@ public static class Setup private const string DB_READ_WRITE_MODE = "ReadWrite"; private const string DB_READ_WRITE_CREATE_MODE = "ReadWriteCreate"; + private static string usedDataFile = string.Empty; + + public static string DataFile => Setup.usedDataFile; + /// /// Tries to migrate the data file. /// @@ -30,7 +34,8 @@ public static class Setup /// public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true) { - serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)}"), ServiceLifetime.Transient); + Setup.usedDataFile = path2DataFile; + serviceCollection.AddDbContext(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"), ServiceLifetime.Transient); } /// From 33fe8f5dac8ff5da0432c922549f1ce7296ce9e9 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:55:32 +0200 Subject: [PATCH 25/43] Added exception handling extension method --- I18N Commander/Processor/ExtensionsError.cs | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 I18N Commander/Processor/ExtensionsError.cs diff --git a/I18N Commander/Processor/ExtensionsError.cs b/I18N Commander/Processor/ExtensionsError.cs new file mode 100644 index 0000000..9cec361 --- /dev/null +++ b/I18N Commander/Processor/ExtensionsError.cs @@ -0,0 +1,27 @@ +using System.Text; +using DataModel; +using Microsoft.Data.Sqlite; + +namespace Processor; + +public static class ExtensionsError +{ + public static ProcessorResult ToProcessorResult(this Exception exception) where TResult : class + { + if(exception.InnerException is SqliteException sqliteException) + if (sqliteException.SqliteErrorCode is 14) + { + var blockingProcesses = SystemUtil.WhoIsLocking(Setup.DataFile); + var sb = new StringBuilder(); + sb.AppendLine($"The data file is used by the {blockingProcesses.Count} other process(es) listed below:"); + foreach (var blockingProcess in blockingProcesses) + sb.AppendLine($"- {blockingProcess.ProcessName} (id={blockingProcess.Id})"); + + return new ProcessorResult(null, false, sb.ToString()); + } + else + return new ProcessorResult(null, false, $"A database error occurred: '{sqliteException.Message}'"); + + return new ProcessorResult(null, false, $"A database error occurred: '{exception.Message}'"); + } +} \ No newline at end of file From 2b4d61334738948e81a81b456ff8fb0f3c29c924 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 12:56:06 +0200 Subject: [PATCH 26/43] Implemented the add text element function --- .../Processor/TextElementProcessor.cs | 48 ++++++++++++++++++- .../Components/TextElements.Designer.cs | 21 ++++---- .../UI WinForms/Components/TextElements.cs | 44 ++++++++++++++++- .../UI WinForms/Components/TextElements.resx | 6 +++ 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index e1578dc..1d31532 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -1,5 +1,4 @@ -using System.Collections; -using DataModel.Database; +using DataModel.Database; using DataModel.Database.Common; using Microsoft.EntityFrameworkCore; @@ -11,4 +10,49 @@ public static class TextElementProcessor { return Task.FromResult(db.TextElements.Where(n => n.Section == section).AsAsyncEnumerable()); } + + public static async Task> AddTextElement(DataContext db, string? currentSectionKey, string elementName) + { + if(string.IsNullOrWhiteSpace(currentSectionKey)) + throw new ArgumentOutOfRangeException(nameof(currentSectionKey)); + + var currentSection = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == currentSectionKey); + if (currentSection is null) + throw new ArgumentOutOfRangeException(nameof(currentSectionKey)); + + // Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: + var code = string.Join('_', elementName.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); + + // Check, if this key already exists: + if (await db.TextElements.AnyAsync(n => n.Section == currentSection && n.Code == code)) + { + var rng = new Random(); + while (await db.TextElements.AnyAsync(n => n.Section == currentSection && n.Code == code)) + { + // Add a random number to the end of the key: + code += $"_{rng.Next(1, 10_000)}"; + } + } + + var textElement = new TextElement + { + Name = elementName, + Code = code, + Section = currentSection, + }; + + // Add the new element to the database: + await db.TextElements.AddAsync(textElement); + + try + { + // Save the changes: + await db.SaveChangesAsync(); + return new ProcessorResult(textElement); + } + catch (DbUpdateException updateException) + { + return updateException.ToProcessorResult(); + } + } } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index 2369d9d..a51a63c 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -37,8 +37,8 @@ this.listTextElements = new System.Windows.Forms.ListBox(); this.textBoxFilter = new System.Windows.Forms.TextBox(); this.labelFilter = new System.Windows.Forms.Label(); - this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.labelSectionPath = new System.Windows.Forms.Label(); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.tableLayout.SuspendLayout(); this.flowLayoutToolbar.SuspendLayout(); this.SuspendLayout(); @@ -90,6 +90,7 @@ this.buttonAdd.TabIndex = 0; this.toolTip.SetToolTip(this.buttonAdd, "Add text element to selected section"); this.buttonAdd.UseVisualStyleBackColor = true; + this.buttonAdd.Click += new System.EventHandler(this.buttonAdd_Click); // // buttonRemove // @@ -103,6 +104,7 @@ this.buttonRemove.TabIndex = 2; this.toolTip.SetToolTip(this.buttonRemove, "Delete this text element"); this.buttonRemove.UseVisualStyleBackColor = true; + this.buttonRemove.Click += new System.EventHandler(this.buttonRemove_Click); // // buttonRename // @@ -116,6 +118,7 @@ this.buttonRename.TabIndex = 1; this.toolTip.SetToolTip(this.buttonRename, "Rename this text element"); this.buttonRename.UseVisualStyleBackColor = true; + this.buttonRename.Click += new System.EventHandler(this.buttonRename_Click); // // listTextElements // @@ -150,14 +153,6 @@ this.labelFilter.Text = "Filter:"; this.labelFilter.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // - // toolTip - // - this.toolTip.AutoPopDelay = 30000; - this.toolTip.InitialDelay = 500; - this.toolTip.ReshowDelay = 100; - this.toolTip.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info; - this.toolTip.ToolTipTitle = "Help"; - // // labelSectionPath // this.labelSectionPath.AutoSize = true; @@ -171,6 +166,14 @@ this.labelSectionPath.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; this.toolTip.SetToolTip(this.labelSectionPath, "The path of the currently selected section"); // + // toolTip + // + this.toolTip.AutoPopDelay = 30000; + this.toolTip.InitialDelay = 500; + this.toolTip.ReshowDelay = 100; + this.toolTip.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info; + this.toolTip.ToolTipTitle = "Help"; + // // TextElements // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index a371e2c..24ad44f 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -2,6 +2,7 @@ using DataModel.Database.Common; using Microsoft.Extensions.DependencyInjection; using Processor; +using UI_WinForms.Dialogs; namespace UI_WinForms.Components; @@ -55,10 +56,51 @@ public partial class TextElements : UserControl { var item = new ListViewItem(textElement.Name) { - Tag = textElement + Tag = textElement.Code, }; this.listTextElements.Items.Add(item); } } + + 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 text element's name.", + Title: "Add a text element", + Placeholder: "My text element", + ShowQuestionCheckbox: false + )); + + if(result.DialogResult == DialogResult.Cancel) + return; + + // Add the text element to the database into the current section: + var newTextElement = await TextElementProcessor.AddTextElement(this.db, this.currentSection?.DataKey, result.Text); + newTextElement.ProcessError(); + + if(!newTextElement.Successful) + return; + + // Add the text element to the list: + var item = new ListViewItem(newTextElement.Result!.Name) + { + Tag = newTextElement.Result.Code, + }; + + this.listTextElements.Items.Add(item); + } + + private void buttonRemove_Click(object sender, EventArgs e) + { + + } + + private void buttonRename_Click(object sender, EventArgs e) + { + + } } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.resx b/I18N Commander/UI WinForms/Components/TextElements.resx index 99de901..01006c8 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.resx +++ b/I18N Commander/UI WinForms/Components/TextElements.resx @@ -60,4 +60,10 @@ 17, 17 + + 17, 17 + + + 17, 17 + \ No newline at end of file From bbefeb97a848d75b552572fb51f8c3dc06d9c7c2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 13:26:25 +0200 Subject: [PATCH 27/43] Added some error handling to the section tree --- I18N Commander/Processor/SectionProcessor.cs | 44 ++++++++++++++----- .../UI WinForms/Components/SectionTree.cs | 16 +++++-- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 2242d94..a1a7844 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -30,7 +30,7 @@ public static class SectionProcessor /// /// Compute the new sections key and its depth, then store the section in the database. /// - public static async Task
AddSection(DataContext db, string text, string? parentKey) + public static async Task> AddSection(DataContext db, string text, string? parentKey) { // Remove any whitespaces from the section name, regardless of how many e.g. spaces the user typed: var key = string.Join('_', text.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); @@ -58,9 +58,16 @@ public static class SectionProcessor TextElements = new(), }; - db.Sections.Add(rootSection); - await db.SaveChangesAsync(); - return rootSection; + try + { + db.Sections.Add(rootSection); + await db.SaveChangesAsync(); + return new ProcessorResult
(rootSection); + } + catch (Exception e) + { + e.ToProcessorResult
(); + } } // Read the parent from the database: @@ -77,10 +84,17 @@ public static class SectionProcessor TextElements = new(), Depth = parent.Depth + 1, }; - - await db.Sections.AddAsync(section); - await db.SaveChangesAsync(); - return section; + + try + { + await db.Sections.AddAsync(section); + await db.SaveChangesAsync(); + return new ProcessorResult
(section); + } + catch (Exception e) + { + return e.ToProcessorResult
(); + } } public static async Task RemoveSection(DataContext db, string selectedKey) @@ -109,7 +123,7 @@ public static class SectionProcessor return await db.Sections.CountAsync(n => n.Parent == section); } - public static async Task
RenameSection(DataContext db, string selectedNodeKey, string alteredName) + public static async Task> RenameSection(DataContext db, string selectedNodeKey, string alteredName) { // Read the section from the database: var section = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == selectedNodeKey); @@ -132,8 +146,16 @@ public static class SectionProcessor section.Name = alteredName; section.DataKey = newKey; - await db.SaveChangesAsync(); - return section; + + try + { + await db.SaveChangesAsync(); + return new ProcessorResult
(section); + } + catch (Exception e) + { + return e.ToProcessorResult
(); + } } public static Section GetSection(DataContext db, string sectionKey) => db.Sections.First(n => n.DataKey == sectionKey); diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs index 2ebbc55..41fc3c3 100644 --- a/I18N Commander/UI WinForms/Components/SectionTree.cs +++ b/I18N Commander/UI WinForms/Components/SectionTree.cs @@ -141,11 +141,15 @@ public partial class SectionTree : UserControl // Add the new section to the database: var addedSection = await SectionProcessor.AddSection(this.db, result.Text, addRootNode ? null : selectedNode?.Name); + addedSection.ProcessError(); + if(!addedSection.Successful) + return; + // Add the new section to the tree control: var node = new TreeNode { - Name = addedSection.DataKey, // [sic] name is the key - Text = addedSection.Name, + Name = addedSection.Result!.DataKey, // [sic] name is the key + Text = addedSection.Result.Name, StateImageIndex = 1, }; @@ -233,8 +237,12 @@ public partial class SectionTree : UserControl // Rename the section: var alteredSection = await SectionProcessor.RenameSection(this.db, selectedNode.Name, result.Text); + alteredSection.ProcessError(); + if(!alteredSection.Successful) + return; + // Rename the selected node: - selectedNode.Text = alteredSection.Name; - selectedNode.Name = alteredSection.DataKey; // [sic] name is the key + selectedNode.Text = alteredSection.Result!.Name; + selectedNode.Name = alteredSection.Result.DataKey; // [sic] name is the key } } \ No newline at end of file From a8e231bb1b56687b19bc42527a97377f29c872da Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 13:31:36 +0200 Subject: [PATCH 28/43] Fixes #27 --- I18N Commander/Processor/SectionProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index a1a7844..7dd6799 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -47,7 +47,7 @@ public static class SectionProcessor } // In the case, when the user adds a section to the root, handle the insert differently: - if (string.IsNullOrEmpty(parentKey)) + if (string.IsNullOrWhiteSpace(parentKey)) { var rootSection = new Section { @@ -66,7 +66,7 @@ public static class SectionProcessor } catch (Exception e) { - e.ToProcessorResult
(); + return e.ToProcessorResult
(); } } From ab27bc206ae6dfe49687f7e11a0b58686f933fc4 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 15:06:21 +0200 Subject: [PATCH 29/43] Added the processor meta class e.g. top handle the service provider --- I18N Commander/Processor/ProcessorMeta.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 I18N Commander/Processor/ProcessorMeta.cs diff --git a/I18N Commander/Processor/ProcessorMeta.cs b/I18N Commander/Processor/ProcessorMeta.cs new file mode 100644 index 0000000..bf57c59 --- /dev/null +++ b/I18N Commander/Processor/ProcessorMeta.cs @@ -0,0 +1,18 @@ +namespace Processor; + +public static class ProcessorMeta +{ + private static IServiceProvider? SERVICE_PROVIDER; + + public static IServiceProvider ServiceProvider + { + get => ProcessorMeta.SERVICE_PROVIDER!; + set + { + if(ProcessorMeta.SERVICE_PROVIDER is not null) + return; + + ProcessorMeta.SERVICE_PROVIDER = value; + } + } +} \ No newline at end of file From f49f6079d55794e8a7654348b09ab9aca1dcb8e7 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 15:14:31 +0200 Subject: [PATCH 30/43] Removed unused processor result --- I18N Commander/UI WinForms/ExtensionsError.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/I18N Commander/UI WinForms/ExtensionsError.cs b/I18N Commander/UI WinForms/ExtensionsError.cs index 0873d09..e21b8ee 100644 --- a/I18N Commander/UI WinForms/ExtensionsError.cs +++ b/I18N Commander/UI WinForms/ExtensionsError.cs @@ -4,12 +4,10 @@ namespace UI_WinForms; public static class ExtensionsError { - public static ProcessorResult ProcessError(this ProcessorResult result) + public static void ProcessError(this ProcessorResult result) { - if (result.Successful) - return result; - + if (result.Successful) return; + MessageBox.Show(result.ErrorMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return result; } } \ No newline at end of file From 36a8efb05d8485be0226cb37ba077bf6c11779cb Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 15:15:55 +0200 Subject: [PATCH 31/43] Refactored service provider usage - Remove service provider from WinForms altogether - Moved service provider handling to processors --- I18N Commander/Processor/SectionProcessor.cs | 57 ++++++++++++++----- .../Processor/TextElementProcessor.cs | 10 +++- .../UI WinForms/Components/SectionTree.cs | 28 +++------ .../UI WinForms/Components/TextElements.cs | 18 ++---- I18N Commander/UI WinForms/Program.cs | 12 ++-- 5 files changed, 68 insertions(+), 57 deletions(-) diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 7dd6799..7b291b7 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -1,6 +1,7 @@ using DataModel.Database; using DataModel.Database.Common; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Processor; @@ -9,16 +10,24 @@ public static class SectionProcessor /// /// Load one layer of the tree by using the specified depth: /// - public static IAsyncEnumerable
LoadLayer(DataContext db, int depth) + public static async Task> LoadLayer(int depth) { - return db.Sections.Where(n => n.Depth == depth).OrderBy(n => n.Id).AsAsyncEnumerable(); + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + var sections = await db.Sections.Where(n => n.Depth == depth).OrderBy(n => n.Id).ToListAsync(); + + // Ensure, that the database loaded the section's parent: + foreach (var section in sections) + await db.Entry(section).Reference(n => n.Parent).LoadAsync(); + + return sections; } /// /// Determine how deep the tree is. /// - public static async ValueTask GetDepth(DataContext db) + public static async ValueTask GetDepth() { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); if(!await db.Sections.AnyAsync()) { return 0; @@ -30,8 +39,10 @@ public static class SectionProcessor /// /// Compute the new sections key and its depth, then store the section in the database. /// - public static async Task> AddSection(DataContext db, string text, string? parentKey) + public static async Task> AddSection(string text, string? parentKey) { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + // Remove any whitespaces from the section name, regardless of how many e.g. spaces the user typed: var key = string.Join('_', text.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); @@ -97,24 +108,32 @@ public static class SectionProcessor } } - public static async Task RemoveSection(DataContext db, string selectedKey) + public static async Task RemoveSection(string selectedKey) + { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + await SectionProcessor.RemoveOneSectionAndItsChildren(db, selectedKey); + await db.SaveChangesAsync(); + } + + private static async Task RemoveOneSectionAndItsChildren(DataContext db, string selectedKey) { // Remove the section from the database: var section2Delete = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == selectedKey); if (section2Delete is null) throw new ArgumentException($"The section with key {selectedKey} does not exist in the database."); - + // Next, remove all children of the section, and the children's children, etc.: var children = await db.Sections.Where(n => n.Parent == section2Delete).ToListAsync(); foreach (var child in children) - await RemoveSection(db, child.DataKey); + await SectionProcessor.RemoveOneSectionAndItsChildren(db, child.DataKey); db.Sections.Remove(section2Delete); - await db.SaveChangesAsync(); } - - public static async Task NumberChildren(DataContext db, string selectedKey) + + public static async Task NumberChildren(string selectedKey) { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + // Read the section from the database: var section = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == selectedKey); if (section is null) @@ -123,8 +142,10 @@ public static class SectionProcessor return await db.Sections.CountAsync(n => n.Parent == section); } - public static async Task> RenameSection(DataContext db, string selectedNodeKey, string alteredName) + public static async Task> RenameSection(string selectedNodeKey, string alteredName) { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + // Read the section from the database: var section = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == selectedNodeKey); if (section is null) @@ -158,10 +179,18 @@ public static class SectionProcessor } } - public static Section GetSection(DataContext db, string sectionKey) => db.Sections.First(n => n.DataKey == sectionKey); - - public static async Task GetSectionPath(DataContext db, string sectionKey) + public static async Task
GetSection(string sectionKey) { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + var section = await db.Sections.FirstAsync(n => n.DataKey == sectionKey); + await db.Entry(section).Reference(n => n.Parent).LoadAsync(); + + return section; + } + + public static async Task GetSectionPath(string sectionKey) + { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); var section = await db.Sections.FirstAsync(n => n.DataKey == sectionKey); // Ensure, that the database loaded the section's parent: diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index 1d31532..82a4718 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -1,18 +1,22 @@ using DataModel.Database; using DataModel.Database.Common; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Processor; public static class TextElementProcessor { - public static Task> GetTextElements(DataContext db, Section section) + public static async Task> GetTextElements(Section section) { - return Task.FromResult(db.TextElements.Where(n => n.Section == section).AsAsyncEnumerable()); + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + return await db.TextElements.Where(n => n.Section == section).ToListAsync(); } - public static async Task> AddTextElement(DataContext db, string? currentSectionKey, string elementName) + public static async Task> AddTextElement(string? currentSectionKey, string elementName) { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + if(string.IsNullOrWhiteSpace(currentSectionKey)) throw new ArgumentOutOfRangeException(nameof(currentSectionKey)); diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs index 41fc3c3..308a38c 100644 --- a/I18N Commander/UI WinForms/Components/SectionTree.cs +++ b/I18N Commander/UI WinForms/Components/SectionTree.cs @@ -1,6 +1,4 @@ -using DataModel.Database.Common; -using Microsoft.Extensions.DependencyInjection; -using Processor; +using Processor; using UI_WinForms.Dialogs; using UI_WinForms.Resources; @@ -8,8 +6,6 @@ namespace UI_WinForms.Components; public partial class SectionTree : UserControl { - private readonly DataContext db; - public SectionTree() { this.InitializeComponent(); @@ -18,12 +14,6 @@ public partial class SectionTree : UserControl if(Program.SERVICE_PROVIDER is null) return; - // Get the DI context from the main form: - this.db = Program.SERVICE_PROVIDER.GetService()!; - - // Dispose of the context when the control is disposed: - this.Disposed += (_, _) => this.db.Dispose(); - // Create an image list from a resource: var imgList = new ImageList(); imgList.ImageSize = new Size(45, 45); @@ -46,7 +36,7 @@ public partial class SectionTree : UserControl var treeNodes = new Dictionary(); // Get the max. depth of the tree: - var maxDepth = await SectionProcessor.GetDepth(this.db); + var maxDepth = await SectionProcessor.GetDepth(); // Store nodes, where we cannot find the parents: var missingParents = new List(); @@ -54,7 +44,7 @@ public partial class SectionTree : UserControl // Populate the tree view out of the database, layer by layer: for (var i = 0; i <= maxDepth; i++) { - await foreach (var section in SectionProcessor.LoadLayer(this.db, i)) + foreach (var section in await SectionProcessor.LoadLayer(i)) { // Create the tree node: var node = new TreeNode @@ -139,7 +129,7 @@ public partial class SectionTree : UserControl var selectedNode = this.treeView.SelectedNode; // Add the new section to the database: - var addedSection = await SectionProcessor.AddSection(this.db, result.Text, addRootNode ? null : selectedNode?.Name); + var addedSection = await SectionProcessor.AddSection(result.Text, addRootNode ? null : selectedNode?.Name); addedSection.ProcessError(); if(!addedSection.Successful) @@ -173,7 +163,7 @@ public partial class SectionTree : UserControl // Get the number of children: // (notice, that the node's name is its key) - var numberChildren = await SectionProcessor.NumberChildren(this.db, selectedNode.Name); + 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) @@ -181,7 +171,7 @@ public partial class SectionTree : UserControl // Remove the section from the database: // (notice, that the node's name is its key) - await SectionProcessor.RemoveSection(this.db, selectedNode.Name); + await SectionProcessor.RemoveSection(selectedNode.Name); // Remove all nodes from the tree control: this.treeView.Nodes.Clear(); @@ -190,7 +180,7 @@ public partial class SectionTree : UserControl this.LoadNodes(this, EventArgs.Empty); } - private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) + private async void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { if(this.DesignMode) return; @@ -205,7 +195,7 @@ public partial class SectionTree : UserControl if (selectedNode is not null) { // Get the section from the database: - var section = SectionProcessor.GetSection(this.db, selectedNode.Name); + var section = await SectionProcessor.GetSection(selectedNode.Name); AppEvents.SectionChanged(section); } } @@ -235,7 +225,7 @@ public partial class SectionTree : UserControl return; // Rename the section: - var alteredSection = await SectionProcessor.RenameSection(this.db, selectedNode.Name, result.Text); + var alteredSection = await SectionProcessor.RenameSection(selectedNode.Name, result.Text); alteredSection.ProcessError(); if(!alteredSection.Successful) diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 24ad44f..081fb1f 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -1,6 +1,4 @@ using DataModel.Database; -using DataModel.Database.Common; -using Microsoft.Extensions.DependencyInjection; using Processor; using UI_WinForms.Dialogs; @@ -8,8 +6,6 @@ namespace UI_WinForms.Components; public partial class TextElements : UserControl { - private readonly DataContext db; - private Section? currentSection; public TextElements() @@ -20,12 +16,6 @@ public partial class TextElements : UserControl if(Program.SERVICE_PROVIDER is null) return; - // Get the DI context from the main form: - this.db = Program.SERVICE_PROVIDER.GetService()!; - - // Dispose of the context when the control is disposed: - this.Disposed += (_, _) => this.db.Dispose(); - // When the section is changed, update this component: AppEvents.WhenSectionChanged += async (sender, section) => { @@ -36,7 +26,7 @@ public partial class TextElements : UserControl if (this.currentSection is null) return; - this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.db, this.currentSection.DataKey); + this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.currentSection.DataKey); this.LoadTextElements(); }; } @@ -48,11 +38,11 @@ public partial class TextElements : UserControl return; // Load the text elements: - var textElements = await TextElementProcessor.GetTextElements(this.db, this.currentSection); + var textElements = await TextElementProcessor.GetTextElements(this.currentSection); // Update the list: this.listTextElements.Items.Clear(); - await foreach (var textElement in textElements) + foreach (var textElement in textElements) { var item = new ListViewItem(textElement.Name) { @@ -79,7 +69,7 @@ public partial class TextElements : UserControl return; // Add the text element to the database into the current section: - var newTextElement = await TextElementProcessor.AddTextElement(this.db, this.currentSection?.DataKey, result.Text); + var newTextElement = await TextElementProcessor.AddTextElement(this.currentSection?.DataKey, result.Text); newTextElement.ProcessError(); if(!newTextElement.Successful) diff --git a/I18N Commander/UI WinForms/Program.cs b/I18N Commander/UI WinForms/Program.cs index a26755f..e871b6e 100644 --- a/I18N Commander/UI WinForms/Program.cs +++ b/I18N Commander/UI WinForms/Program.cs @@ -2,6 +2,7 @@ using DataModel; using DataModel.Database.Common; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Processor; namespace UI_WinForms; @@ -34,9 +35,6 @@ internal static class Program // builder.ConfigureServices((hostContext, serviceCollection) => { - // The main form: - serviceCollection.AddSingleton
(); - // The database: serviceCollection.AddDatabase(loader.DataFile, true); }); @@ -50,15 +48,15 @@ internal static class Program // Get a service provider: SERVICE_PROVIDER = scope.ServiceProvider; + // Set the service provider to the processor: + ProcessorMeta.ServiceProvider = SERVICE_PROVIDER; + // Apply database migrations: using (var database = SERVICE_PROVIDER.GetRequiredService()) Setup.PerformDataMigration(database).Wait(); - // Create the main window: - var mainWindow = SERVICE_PROVIDER.GetService
(); - // Start the app: - Application.Run(mainWindow); + Application.Run(new Main()); } } } \ No newline at end of file From 0364c5442754edf0a5b7bf09a46127f77dda5f37 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 15:49:27 +0200 Subject: [PATCH 32/43] Added detection of ransomware protection --- I18N Commander/Processor/ExtensionsError.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/I18N Commander/Processor/ExtensionsError.cs b/I18N Commander/Processor/ExtensionsError.cs index 9cec361..ed058c9 100644 --- a/I18N Commander/Processor/ExtensionsError.cs +++ b/I18N Commander/Processor/ExtensionsError.cs @@ -17,6 +17,13 @@ public static class ExtensionsError foreach (var blockingProcess in blockingProcesses) sb.AppendLine($"- {blockingProcess.ProcessName} (id={blockingProcess.Id})"); + // Is there only one process and it is this one? + if (blockingProcesses.Count == 1 && blockingProcesses.First().ProcessName == "I18N Commander") + { + sb.AppendLine(); + sb.AppendLine("Hint: Is the ransomware protection enabled in your Windows system? If so, please make sure that the I18N Commander has write permission."); + } + return new ProcessorResult(null, false, sb.ToString()); } else From ccc858830beb07f56276967d4c195ac266cfa061 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 16:00:32 +0200 Subject: [PATCH 33/43] Applied an order --- I18N Commander/Processor/TextElementProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index 82a4718..be918a9 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -10,7 +10,7 @@ public static class TextElementProcessor public static async Task> GetTextElements(Section section) { await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); - return await db.TextElements.Where(n => n.Section == section).ToListAsync(); + return await db.TextElements.Where(n => n.Section == section).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync(); } public static async Task> AddTextElement(string? currentSectionKey, string elementName) From fa36300399b02592546aa21c284d7b0dceb6d58b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 16:01:11 +0200 Subject: [PATCH 34/43] Refactored the listbox into a listview --- .../Components/TextElements.Designer.cs | 35 ++++++++++--------- .../UI WinForms/Components/TextElements.resx | 6 ---- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index a51a63c..6313a59 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -34,10 +34,10 @@ this.buttonAdd = new System.Windows.Forms.Button(); this.buttonRemove = new System.Windows.Forms.Button(); this.buttonRename = new System.Windows.Forms.Button(); - this.listTextElements = new System.Windows.Forms.ListBox(); this.textBoxFilter = new System.Windows.Forms.TextBox(); this.labelFilter = new System.Windows.Forms.Label(); this.labelSectionPath = new System.Windows.Forms.Label(); + this.listTextElements = new System.Windows.Forms.ListView(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.tableLayout.SuspendLayout(); this.flowLayoutToolbar.SuspendLayout(); @@ -50,10 +50,10 @@ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayout.Controls.Add(this.flowLayoutToolbar, 0, 0); - this.tableLayout.Controls.Add(this.listTextElements, 1, 1); this.tableLayout.Controls.Add(this.textBoxFilter, 2, 2); this.tableLayout.Controls.Add(this.labelFilter, 1, 2); this.tableLayout.Controls.Add(this.labelSectionPath, 1, 0); + this.tableLayout.Controls.Add(this.listTextElements, 1, 1); this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayout.Location = new System.Drawing.Point(0, 0); this.tableLayout.Name = "tableLayout"; @@ -120,19 +120,6 @@ this.buttonRename.UseVisualStyleBackColor = true; this.buttonRename.Click += new System.EventHandler(this.buttonRename_Click); // - // listTextElements - // - this.tableLayout.SetColumnSpan(this.listTextElements, 2); - this.listTextElements.Dock = System.Windows.Forms.DockStyle.Fill; - this.listTextElements.FormattingEnabled = true; - this.listTextElements.IntegralHeight = false; - this.listTextElements.ItemHeight = 28; - this.listTextElements.Location = new System.Drawing.Point(69, 43); - this.listTextElements.Name = "listTextElements"; - this.listTextElements.ScrollAlwaysVisible = true; - this.listTextElements.Size = new System.Drawing.Size(634, 115); - this.listTextElements.TabIndex = 1; - // // textBoxFilter // this.textBoxFilter.Dock = System.Windows.Forms.DockStyle.Fill; @@ -166,6 +153,22 @@ this.labelSectionPath.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; this.toolTip.SetToolTip(this.labelSectionPath, "The path of the currently selected section"); // + // listTextElements + // + this.tableLayout.SetColumnSpan(this.listTextElements, 2); + this.listTextElements.Dock = System.Windows.Forms.DockStyle.Fill; + this.listTextElements.FullRowSelect = true; + this.listTextElements.GridLines = true; + this.listTextElements.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None; + this.listTextElements.Location = new System.Drawing.Point(69, 43); + this.listTextElements.MultiSelect = false; + this.listTextElements.Name = "listTextElements"; + this.listTextElements.ShowGroups = false; + this.listTextElements.Size = new System.Drawing.Size(634, 115); + this.listTextElements.TabIndex = 5; + this.listTextElements.UseCompatibleStateImageBehavior = false; + this.listTextElements.View = System.Windows.Forms.View.List; + // // toolTip // this.toolTip.AutoPopDelay = 30000; @@ -197,9 +200,9 @@ private Button buttonRename; private Button buttonRemove; private ToolTip toolTip; - private ListBox listTextElements; private TextBox textBoxFilter; private Label labelFilter; private Label labelSectionPath; + private ListView listTextElements; } } diff --git a/I18N Commander/UI WinForms/Components/TextElements.resx b/I18N Commander/UI WinForms/Components/TextElements.resx index 01006c8..99de901 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.resx +++ b/I18N Commander/UI WinForms/Components/TextElements.resx @@ -60,10 +60,4 @@ 17, 17 - - 17, 17 - - - 17, 17 - \ No newline at end of file From a7cef8d20941c4be41c21d6d4a2023f3307940ee Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Jul 2022 20:37:25 +0200 Subject: [PATCH 35/43] Added text element icon --- .../UI WinForms/Resources/Icons.Designer.cs | 10 ++++++++++ I18N Commander/UI WinForms/Resources/Icons.resx | 3 +++ .../Resources/icons8-align-text-left-512.png | Bin 0 -> 2182 bytes 3 files changed, 13 insertions(+) create mode 100644 I18N Commander/UI WinForms/Resources/icons8-align-text-left-512.png diff --git a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs index 42848c3..8311735 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs +++ b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs @@ -80,6 +80,16 @@ namespace UI_WinForms.Resources { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_align_text_left_512 { + get { + object obj = ResourceManager.GetObject("icons8_align_text_left_512", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/I18N Commander/UI WinForms/Resources/Icons.resx b/I18N Commander/UI WinForms/Resources/Icons.resx index dc4a41d..41a3205 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.resx +++ b/I18N Commander/UI WinForms/Resources/Icons.resx @@ -124,6 +124,9 @@ icons8-add-tag-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + icons8-align-text-left-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + icons8-browse-folder-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/I18N Commander/UI WinForms/Resources/icons8-align-text-left-512.png b/I18N Commander/UI WinForms/Resources/icons8-align-text-left-512.png new file mode 100644 index 0000000000000000000000000000000000000000..4e67e1d1b67ad0c06ab0b112c0d1be8bd82c7034 GIT binary patch literal 2182 zcmai0X;c$e6rQ+{8nLpY)ap1QXdyF66llgoiAgG3fDqIKtrC)fjF3#63?xuR#fn<( zL8uE__>N}&oW)w-}01r=*mdR%_gwQ992Dk^T&z9a~>YWri7y!UZue0wHeI-hak~3 zn^~(%Cm1+|NFz;Rr1MZ60+V_%GCM@UQ<$ZMfsDwZ2~|#PqAn+0C)6X;rgB6!90(W* zMhn}F872#E6C(~?9Q?L#V+ib^FzI4Mt%!%E6ivV(Xb8$frgC5rtxv@hWS{gJgOwOD zFpL?;Fss#yS|_0tordv+LLtTzU;+UbFu0a%6Qi|pO_p(X38#jPu;^&g%#f4`wrgrr zs7ywTAOMGZ#ThcSU)^NsJw8YTW=AkS%ESIiCv4<9=yv1{y4gT6l*K@q`wsA$i$3@b zLy*3=o7lAGw~-Z!|E3#_uN}j}$g@BqdXxHEOs^bR60^+&mOxmjOj<|Cvj`J2&XEaw zUhqhxHjRiOOlgckj7$#s7ciUD|9jsql#yw##7q+jTnqY5)cu$k2@_!aen|6H$ewCE zp0p7eY8h!HOctQp7ezm)c?T$^GALRF+L{m}PG5a>z#W)^(gSFcBaD71QYww7sZ=rp zV3vd#)8R4s6NPY?+n;ECCPnCNGT34@=~S;=E9t9~#6H;5?o~ z*}(>PL9b=B|Hrp?G%NxZ3I!fPS{RDXc5FG@TtzH&uAIvZ($U{A?5HxX)!DNyM$9y& z&(slmhjSq3L@iV*W7X1xBn=c;j7U;bNl>3`INsj5FdyafP-k`fEUX3ssJ;7aSZ`#Q z{l>itO@zG>yvRZL#}x&3&OLDT!3@Frh6q^njUmDWW)BU9%w2zv-yw*VM}o4vQ4R>2 zP?%YGD0-m8ZDpN*{;DS5B(++mtdLEfex?a&@ZBk2oBV5lu*>bi#~mL27db!VJ7I~BaQtDUmu|E8(RPl!s|eP<;Tuw!s@&#tHU@>=e% zUGC#o-8xdqWaeZA%Vs`5(3sNnL4l^?*^f)U&lQ#hC6{}TRwi--MPo%ZTU3h6J|?D{7ydajr2(i+Xh4Ns1ponPr+v#;v#)(+;Q-RnDbwMR#` zoccMc&7(6s@c75Mb7KTi(3pX)Ep3g->l0@~2_RUA48k4>9zj-F5 z8k7vZE;cKjs+YL4v3g}G{E>~taxXpFaH2smR>~ecd&>C}%uV>zm>lELx(Vv&4l(LqTumakJp32ojiqu~`?s<8r)?#zf z|E1~nz9?YYrF+zSNd3~S->Qd)BqtrbxZfbX@ZemE#63E$Ibq+o?k%?Nhh^K-eRI{j_Vrq@f;hW86;Nq6@k&SHbR! z2hEN8;!!+%YU!HsU)qAWK3=-kRe|*-%@0-53mq@co2A$9j6WQ9y#@&S&(Bu6N1xR$ zxhYf*r1N(jiS~*1yRS?*-V}6?`)M$F)JRruTUEM4H!+DQC~LP~9yL?GV(}QmCC|i? z3TDTKI_dFYm3wO@+$qipet4;IRK>=nS$`C{w7Nun=gA6;>pbgyc}QGqcy8Q)$BB~N oY3Sk`=iSI_^sS Date: Sun, 17 Jul 2022 20:47:17 +0200 Subject: [PATCH 36/43] Fixed listview to look & behave like a listbox, but with icons --- .../Components/TextElements.Designer.cs | 12 ++++++-- .../UI WinForms/Components/TextElements.cs | 28 ++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index 6313a59..316636e 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -38,6 +38,7 @@ this.labelFilter = new System.Windows.Forms.Label(); this.labelSectionPath = new System.Windows.Forms.Label(); this.listTextElements = new System.Windows.Forms.ListView(); + this.column = new System.Windows.Forms.ColumnHeader(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.tableLayout.SuspendLayout(); this.flowLayoutToolbar.SuspendLayout(); @@ -155,19 +156,23 @@ // // listTextElements // + this.listTextElements.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.column}); this.tableLayout.SetColumnSpan(this.listTextElements, 2); this.listTextElements.Dock = System.Windows.Forms.DockStyle.Fill; this.listTextElements.FullRowSelect = true; - this.listTextElements.GridLines = true; this.listTextElements.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None; this.listTextElements.Location = new System.Drawing.Point(69, 43); this.listTextElements.MultiSelect = false; this.listTextElements.Name = "listTextElements"; - this.listTextElements.ShowGroups = false; this.listTextElements.Size = new System.Drawing.Size(634, 115); this.listTextElements.TabIndex = 5; this.listTextElements.UseCompatibleStateImageBehavior = false; - this.listTextElements.View = System.Windows.Forms.View.List; + this.listTextElements.View = System.Windows.Forms.View.Details; + // + // column + // + this.column.Width = 194; // // toolTip // @@ -204,5 +209,6 @@ private Label labelFilter; private Label labelSectionPath; private ListView listTextElements; + private ColumnHeader column; } } diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 081fb1f..649aded 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -1,6 +1,7 @@ using DataModel.Database; using Processor; using UI_WinForms.Dialogs; +using UI_WinForms.Resources; namespace UI_WinForms.Components; @@ -16,6 +17,15 @@ public partial class TextElements : UserControl 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_align_text_left_512); + + // Set the image list to the list box: + this.listTextElements.SmallImageList = imgList; + // When the section is changed, update this component: AppEvents.WhenSectionChanged += async (sender, section) => { @@ -43,14 +53,12 @@ public partial class TextElements : UserControl // Update the list: this.listTextElements.Items.Clear(); foreach (var textElement in textElements) - { - var item = new ListViewItem(textElement.Name) + this.listTextElements.Items.Add(new ListViewItem(textElement.Name, 0) { Tag = textElement.Code, - }; - - this.listTextElements.Items.Add(item); - } + }); + + this.column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); } private async void buttonAdd_Click(object sender, EventArgs e) @@ -76,12 +84,12 @@ public partial class TextElements : UserControl return; // Add the text element to the list: - var item = new ListViewItem(newTextElement.Result!.Name) + this.listTextElements.Items.Add(new ListViewItem(newTextElement.Result!.Name, 0) { Tag = newTextElement.Result.Code, - }; - - this.listTextElements.Items.Add(item); + }); + + this.column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); } private void buttonRemove_Click(object sender, EventArgs e) From 0e4a99332b3b03991cd0c5f90fc36f4dfe62d158 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 18 Jul 2022 21:32:39 +0200 Subject: [PATCH 37/43] Fixes #29 by generating valid keys --- I18N Commander/Processor/SectionProcessor.cs | 17 ++----- .../Processor/TextElementProcessor.cs | 15 +----- I18N Commander/Processor/Utils.cs | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 I18N Commander/Processor/Utils.cs diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 7b291b7..f66275b 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -42,21 +42,10 @@ public static class SectionProcessor public static async Task> AddSection(string text, string? parentKey) { await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); - - // Remove any whitespaces from the section name, regardless of how many e.g. spaces the user typed: - var key = string.Join('_', text.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); - - // Check, if this key already exists: - if (await db.Sections.AnyAsync(n => n.DataKey == key)) - { - var rng = new Random(); - while (await db.Sections.AnyAsync(n => n.DataKey == key)) - { - // Add a random number to the end of the key: - key += $"_{rng.Next(1, 10_000)}"; - } - } + // Generate the key: + var key = await Utils.GenerateCode(text, db.Sections, (n, key) => n.DataKey == key); + // In the case, when the user adds a section to the root, handle the insert differently: if (string.IsNullOrWhiteSpace(parentKey)) { diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index be918a9..c8e304b 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -23,20 +23,9 @@ public static class TextElementProcessor var currentSection = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == currentSectionKey); if (currentSection is null) throw new ArgumentOutOfRangeException(nameof(currentSectionKey)); - - // Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: - var code = string.Join('_', elementName.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); - // Check, if this key already exists: - if (await db.TextElements.AnyAsync(n => n.Section == currentSection && n.Code == code)) - { - var rng = new Random(); - while (await db.TextElements.AnyAsync(n => n.Section == currentSection && n.Code == code)) - { - // Add a random number to the end of the key: - code += $"_{rng.Next(1, 10_000)}"; - } - } + // Generate a code: + var code = await Utils.GenerateCode(elementName, db.TextElements, (n, code) => n.Section == currentSection && n.Code == code); var textElement = new TextElement { diff --git a/I18N Commander/Processor/Utils.cs b/I18N Commander/Processor/Utils.cs new file mode 100644 index 0000000..9c19df2 --- /dev/null +++ b/I18N Commander/Processor/Utils.cs @@ -0,0 +1,49 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Processor; + +internal static class Utils +{ + private static readonly Random RNG = new(); + + /// + /// Generates a code out of this name. + /// + /// The name where the code based on + /// The data class + /// The selector to check, if that key already exists. The string parameter is the current code to check. + /// The generated code + internal static async Task GenerateCode(string name, DbSet db, Expression> selector) where TDbSet : class + { + // Filter all non-alphanumeric characters from the name by allowing only A-Z, a-z, 0-9, and spaces from the ASCII table: + name = new string(name.Where(c => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9' or ' ').ToArray()); + + // Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: + var code = string.Join('_', name.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); + + // + // The Any() query want's an Expression, but we have an Expression, though. + // Therefore, we have to currying the string away: + var typeDbSet = Expression.Parameter(typeof(TDbSet), null); + var curriedSelector = Expression.Lambda>( + Expression.Invoke(selector, typeDbSet, Expression.Constant(code)), + typeDbSet + ); + + // Check, if this key already exists. If so, add a random number to the end of the key: + if (await db.AnyAsync(curriedSelector)) + while (await db.AnyAsync(curriedSelector)) + { + code += $"_{RNG.Next(1, 10_000)}"; + + // Due to the changed code & since the string is a constant, we have to re-currying the string away: + curriedSelector = Expression.Lambda>( + Expression.Invoke(selector, typeDbSet, Expression.Constant(code)), + typeDbSet + ); + } + + return code; + } +} \ No newline at end of file From af47eb606c75a0092a4cd74a495fbb963da900ee Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 20 Jul 2022 20:51:12 +0200 Subject: [PATCH 38/43] Added event when a text element gets selected --- .../Processor/TextElementProcessor.cs | 9 +++++++ I18N Commander/UI WinForms/AppEvents.cs | 8 ++++++ .../Components/TextElements.Designer.cs | 1 + .../UI WinForms/Components/TextElements.cs | 26 ++++++++++++++++--- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index c8e304b..a2ea7e0 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -7,12 +7,21 @@ namespace Processor; public static class TextElementProcessor { + // Load all text elements for one particular section: public static async Task> GetTextElements(Section section) { await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); return await db.TextElements.Where(n => n.Section == section).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync(); } + + // Load one text element by id: + public static async Task LoadTextElement(int id) + { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + return await db.TextElements.FirstAsync(n => n.Id == id); + } + // Adds a new text element: public static async Task> AddTextElement(string? currentSectionKey, string elementName) { await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); diff --git a/I18N Commander/UI WinForms/AppEvents.cs b/I18N Commander/UI WinForms/AppEvents.cs index 41191ae..f4ae16a 100644 --- a/I18N Commander/UI WinForms/AppEvents.cs +++ b/I18N Commander/UI WinForms/AppEvents.cs @@ -4,7 +4,15 @@ namespace UI_WinForms; internal static class AppEvents { + // Section changed event which can be subscribed: internal static event EventHandler
WhenSectionChanged; + // Method to raise the section changed event: internal static void SectionChanged(Section section) => AppEvents.WhenSectionChanged?.Invoke(null, section); + + // Text element changed event which can be subscribed: + internal static event EventHandler WhenTextElementChanged; + + // Method to raise the text element changed event: + internal static void TextElementChanged(TextElement textElement) => AppEvents.WhenTextElementChanged?.Invoke(null, textElement); } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index 316636e..dd7196d 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -169,6 +169,7 @@ this.listTextElements.TabIndex = 5; this.listTextElements.UseCompatibleStateImageBehavior = false; this.listTextElements.View = System.Windows.Forms.View.Details; + this.listTextElements.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.listTextElements_ItemSelectionChanged); // // column // diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index 649aded..aec4ea3 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -8,6 +8,7 @@ namespace UI_WinForms.Components; public partial class TextElements : UserControl { private Section? currentSection; + private TextElement? currentTextElement; public TextElements() { @@ -30,15 +31,24 @@ public partial class TextElements : UserControl AppEvents.WhenSectionChanged += async (sender, section) => { this.currentSection = section; + this.currentTextElement = null; this.buttonAdd.Enabled = this.currentSection is not null; + this.buttonRename.Enabled = this.buttonRemove.Enabled = false; - // Update the path: if (this.currentSection is null) return; + // Update the path: this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.currentSection.DataKey); this.LoadTextElements(); }; + + // When the text element is changed, update the button states: + AppEvents.WhenTextElementChanged += (sender, textElement) => + { + this.currentTextElement = textElement; + this.buttonRename.Enabled = this.buttonRemove.Enabled = this.currentTextElement is not null; + }; } // Loads all the text elements for the current section. @@ -55,7 +65,7 @@ public partial class TextElements : UserControl foreach (var textElement in textElements) this.listTextElements.Items.Add(new ListViewItem(textElement.Name, 0) { - Tag = textElement.Code, + Tag = textElement.Id, }); this.column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); @@ -86,7 +96,7 @@ public partial class TextElements : UserControl // Add the text element to the list: this.listTextElements.Items.Add(new ListViewItem(newTextElement.Result!.Name, 0) { - Tag = newTextElement.Result.Code, + Tag = newTextElement.Result.Id, }); this.column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); @@ -101,4 +111,14 @@ public partial class TextElements : UserControl { } + + private async void listTextElements_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e) + { + // Load the text element: + var selectedTextElementId = (int)e.Item.Tag; + this.currentTextElement = await TextElementProcessor.LoadTextElement(selectedTextElementId); + + // Fire the event: + AppEvents.TextElementChanged(this.currentTextElement); + } } \ No newline at end of file From 1efe7346b0ae28ba2c9f95b14260e2e8f1333d23 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 20 Jul 2022 20:56:20 +0200 Subject: [PATCH 39/43] Changed default size to be full hd & increased height for top panel --- I18N Commander/UI WinForms/Components/Main.Designer.cs | 6 +++--- I18N Commander/UI WinForms/Main.Designer.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/I18N Commander/UI WinForms/Components/Main.Designer.cs b/I18N Commander/UI WinForms/Components/Main.Designer.cs index efcf16a..7822135 100644 --- a/I18N Commander/UI WinForms/Components/Main.Designer.cs +++ b/I18N Commander/UI WinForms/Components/Main.Designer.cs @@ -110,9 +110,9 @@ // splitContainerRTB.Panel1 // this.splitContainerRTB.Panel1.Controls.Add(this.textElements); - this.splitContainerRTB.Panel1MinSize = 240; + this.splitContainerRTB.Panel1MinSize = 340; this.splitContainerRTB.Size = new System.Drawing.Size(636, 531); - this.splitContainerRTB.SplitterDistance = 240; + this.splitContainerRTB.SplitterDistance = 340; this.splitContainerRTB.TabIndex = 0; // // textElements @@ -121,7 +121,7 @@ this.textElements.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.textElements.Location = new System.Drawing.Point(0, 0); this.textElements.Name = "textElements"; - this.textElements.Size = new System.Drawing.Size(634, 238); + this.textElements.Size = new System.Drawing.Size(634, 338); this.textElements.TabIndex = 0; // // Main diff --git a/I18N Commander/UI WinForms/Main.Designer.cs b/I18N Commander/UI WinForms/Main.Designer.cs index 2944fd8..edf901e 100644 --- a/I18N Commander/UI WinForms/Main.Designer.cs +++ b/I18N Commander/UI WinForms/Main.Designer.cs @@ -38,14 +38,14 @@ this.mainComponent.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.mainComponent.Location = new System.Drawing.Point(0, 0); this.mainComponent.Name = "mainComponent"; - this.mainComponent.Size = new System.Drawing.Size(1071, 755); + this.mainComponent.Size = new System.Drawing.Size(1902, 1033); this.mainComponent.TabIndex = 0; // // Main // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.ClientSize = new System.Drawing.Size(1071, 755); + this.ClientSize = new System.Drawing.Size(1902, 1033); this.Controls.Add(this.mainComponent); this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); From 9ad19efc46028020b748405ff003a453b2af6055 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 21 Jul 2022 20:35:22 +0200 Subject: [PATCH 40/43] Implemented the text element rename function --- .../Processor/TextElementProcessor.cs | 30 +++++++++++++++++++ .../UI WinForms/Components/TextElements.cs | 29 +++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index a2ea7e0..48e7562 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -57,4 +57,34 @@ public static class TextElementProcessor return updateException.ToProcessorResult(); } } + + // Renames a text element: + public static async Task> RenameTextElement(int id, string newName) + { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + + var textElement = await db.TextElements.FirstAsync(n => n.Id == id); + if (textElement is null) + throw new ArgumentOutOfRangeException(nameof(id)); + + // Get the corresponding section: + var section = (await db.TextElements.FirstAsync(n => n.Id == id)).Section; + + // Generate a code: + var code = await Utils.GenerateCode(newName, db.TextElements, (n, code) => n.Section == section && n.Code == code); + + textElement.Name = newName; + textElement.Code = code; + + // Save the changes: + try + { + await db.SaveChangesAsync(); + return new ProcessorResult(textElement); + } + catch (DbUpdateException updateException) + { + return updateException.ToProcessorResult(); + } + } } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index aec4ea3..f1e5e2a 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -107,9 +107,36 @@ public partial class TextElements : UserControl } - private void buttonRename_Click(object sender, EventArgs e) + private async void buttonRename_Click(object sender, EventArgs e) { + if(this.DesignMode) + return; + + // Ask the user if he really wants to rename the text element: + if(MessageBox.Show("Are you sure, you want to rename the selected text element? If you are already using this element in your code, you will need to manually refactor your code after renaming it.", "Rename text element", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.No) + return; + // Ask the user for the new name: + var result = InputDialog.Show(new InputDialog.Options( + Message: "Please edit the text element's name.", + PreloadedText: this.currentTextElement!.Name, + ShowQuestionCheckbox: false, + Title: "Rename text element" + )); + + // If the user canceled, return: + if(result.DialogResult == DialogResult.Cancel) + return; + + // Rename the text element: + var alteredTextElement = await TextElementProcessor.RenameTextElement(this.currentTextElement.Id, result.Text); + + alteredTextElement.ProcessError(); + if(!alteredTextElement.Successful) + return; + + // Reload the text elements: + this.LoadTextElements(); } private async void listTextElements_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e) From e08c94177a3b973a57a99c794b6c70b6121caa3c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 21 Jul 2022 21:02:22 +0200 Subject: [PATCH 41/43] Improved text element's list view --- I18N Commander/UI WinForms/Components/TextElements.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index dd7196d..8d5c900 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -168,7 +168,7 @@ this.listTextElements.Size = new System.Drawing.Size(634, 115); this.listTextElements.TabIndex = 5; this.listTextElements.UseCompatibleStateImageBehavior = false; - this.listTextElements.View = System.Windows.Forms.View.Details; + this.listTextElements.View = System.Windows.Forms.View.SmallIcon; this.listTextElements.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.listTextElements_ItemSelectionChanged); // // column From e762cfe21e85fa23cb67ddc9c0331ff35b4146e5 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 21 Jul 2022 21:14:32 +0200 Subject: [PATCH 42/43] Implemented the text element filter --- I18N Commander/Processor/TextElementProcessor.cs | 8 ++++++-- .../Components/TextElements.Designer.cs | 1 + .../UI WinForms/Components/TextElements.cs | 14 ++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index 48e7562..8c63e17 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -8,10 +8,14 @@ namespace Processor; public static class TextElementProcessor { // Load all text elements for one particular section: - public static async Task> GetTextElements(Section section) + public static async Task> GetTextElements(Section section, string filterTerm) { await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); - return await db.TextElements.Where(n => n.Section == section).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync(); + + if(string.IsNullOrWhiteSpace(filterTerm)) + return await db.TextElements.Where(n => n.Section == section).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync(); + else + return await db.TextElements.Where(n => n.Section == section && n.Name.Contains(filterTerm)).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync(); } // Load one text element by id: diff --git a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs index 8d5c900..a9a5cfd 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.Designer.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.Designer.cs @@ -129,6 +129,7 @@ this.textBoxFilter.Size = new System.Drawing.Size(554, 34); this.textBoxFilter.TabIndex = 2; this.textBoxFilter.WordWrap = false; + this.textBoxFilter.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxFilter_KeyUp); // // labelFilter // diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index f1e5e2a..aa6e354 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -9,7 +9,7 @@ public partial class TextElements : UserControl { private Section? currentSection; private TextElement? currentTextElement; - + public TextElements() { this.InitializeComponent(); @@ -17,7 +17,7 @@ public partial class TextElements : UserControl // 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); @@ -40,7 +40,7 @@ public partial class TextElements : UserControl // Update the path: this.labelSectionPath.Text = await SectionProcessor.GetSectionPath(this.currentSection.DataKey); - this.LoadTextElements(); + await this.LoadTextElements(); }; // When the text element is changed, update the button states: @@ -52,13 +52,13 @@ public partial class TextElements : UserControl } // Loads all the text elements for the current section. - private async void LoadTextElements() + private async Task LoadTextElements() { if (this.currentSection is null) return; // Load the text elements: - var textElements = await TextElementProcessor.GetTextElements(this.currentSection); + var textElements = await TextElementProcessor.GetTextElements(this.currentSection, this.textBoxFilter.Text); // Update the list: this.listTextElements.Items.Clear(); @@ -136,7 +136,7 @@ public partial class TextElements : UserControl return; // Reload the text elements: - this.LoadTextElements(); + await this.LoadTextElements(); } private async void listTextElements_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e) @@ -148,4 +148,6 @@ public partial class TextElements : UserControl // Fire the event: AppEvents.TextElementChanged(this.currentTextElement); } + + private async void textBoxFilter_KeyUp(object sender, KeyEventArgs e) => await this.LoadTextElements(); } \ No newline at end of file From 71936e5dcf139ec0e8069192de693835e5b7138e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 23 Jul 2022 21:21:48 +0200 Subject: [PATCH 43/43] Implemented the delete function for text elements --- .../Processor/TextElementProcessor.cs | 19 +++++++++++++++++++ .../UI WinForms/Components/TextElements.cs | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index 8c63e17..7f30adc 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -91,4 +91,23 @@ public static class TextElementProcessor return updateException.ToProcessorResult(); } } + + // Deletes a text element: + public static async Task DeleteTextElement(int id) + { + await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); + var textElement = await db.TextElements.FirstAsync(n => n.Id == id); + + // Remove the element from the database: + db.TextElements.Remove(textElement); + + try + { + // Save the changes: + await db.SaveChangesAsync(); + } + catch (DbUpdateException updateException) + { + } + } } \ No newline at end of file diff --git a/I18N Commander/UI WinForms/Components/TextElements.cs b/I18N Commander/UI WinForms/Components/TextElements.cs index aa6e354..abed227 100644 --- a/I18N Commander/UI WinForms/Components/TextElements.cs +++ b/I18N Commander/UI WinForms/Components/TextElements.cs @@ -102,9 +102,20 @@ public partial class TextElements : UserControl this.column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent); } - private void buttonRemove_Click(object sender, EventArgs e) + private async void buttonRemove_Click(object sender, EventArgs e) { + if(this.DesignMode || this.currentTextElement is null) + return; + // Ask the user, if he really wants to remove the text element: + if(MessageBox.Show(this.currentTextElement.Translations.Count > 0 ? $"Are you sure, you want to remove the text element '{this.currentTextElement.Name}', its {this.currentTextElement.Translations.Count} translations and so on?" : $"Are you sure, you want to remove the text element '{this.currentTextElement.Name}'?", "Remove text element", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.No) + return; + + // Remove the text element1 from the database: + await TextElementProcessor.DeleteTextElement(this.currentTextElement.Id); + + // Reload the data: + await this.LoadTextElements(); } private async void buttonRename_Click(object sender, EventArgs e)