diff --git a/GT.RText-Release/GT.NDB0.Core.dll b/GT.RText-Release/GT.NDB0.Core.dll
new file mode 100644
index 0000000..8215334
Binary files /dev/null and b/GT.RText-Release/GT.NDB0.Core.dll differ
diff --git a/GT.RText-Release/GT.RText.Core.dll b/GT.RText-Release/GT.RText.Core.dll
new file mode 100644
index 0000000..6b63981
Binary files /dev/null and b/GT.RText-Release/GT.RText.Core.dll differ
diff --git a/GT.RText-Release/GT.RText.exe b/GT.RText-Release/GT.RText.exe
new file mode 100644
index 0000000..c60959a
Binary files /dev/null and b/GT.RText-Release/GT.RText.exe differ
diff --git a/GT.RText-Release/GT.RText.exe.config b/GT.RText-Release/GT.RText.exe.config
new file mode 100644
index 0000000..56efbc7
--- /dev/null
+++ b/GT.RText-Release/GT.RText.exe.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GT.RText-Release/GT.Shared.dll b/GT.RText-Release/GT.Shared.dll
new file mode 100644
index 0000000..284b7f8
Binary files /dev/null and b/GT.RText-Release/GT.Shared.dll differ
diff --git a/GT.RText-Release/README.txt b/GT.RText-Release/README.txt
new file mode 100644
index 0000000..aa973bf
--- /dev/null
+++ b/GT.RText-Release/README.txt
@@ -0,0 +1,72 @@
+GT.RText v1.9.4 - Enhanced Edition
+=====================================
+
+IMPORTANT NOTICE
+----------------
+This is a MODIFIED version of the original GT.RText by xfileFIN
+Original author: xfileFIN - Copyright © 2022
+Enhanced by: Derek W after requested by a friend - 2025
+This version includes additional features not present in the original
+
+OVERVIEW
+--------
+Gran Turismo RText Editor with enhanced features including:
+- CSV Import/Export functionality for bulk editing
+- Pure black dark theme with dark title bar
+- Custom icon support
+- English interface and improved usability
+
+CREDITS
+-------
+• Original GT.RText: xfileFIN - 2022
+• Enhanced Edition: Derek W - 2025
+• Additional Features: CSV Import/Export, Dark Theme, UI Improvements
+
+SYSTEM REQUIREMENTS
+-------------------
+- Windows 10/11
+- .NET Framework 4.7.2 or higher
+- 50MB free disk space
+
+INSTALLATION
+------------
+1. Extract all files to a folder of your choice
+2. Ensure all DLL files are in the same folder as GT.RText.exe
+3. Run GT.RText.exe
+
+FEATURES
+--------
+ Dark Theme: Pure black interface with dark window title bar
+ CSV Import: Import text entries for bulk editing from CSV files
+ CSV Export: Export category data to CSV format for backup/editing
+ Icon Support: Custom application icon (app.ico)
+ English Interface: Fully translated interface and comments
+ Enhanced Compatibility: Works with Excel CSV formats
+
+USAGE
+-----
+1. Open a Gran Turismo RText file (.rt03, .rt04, .rt05, ._50tr)
+2. Right-click on a category to access Import/Export options
+3. Use "Import this category from CSV" for bulk updates
+4. Use "Export this category to CSV" for data backup
+5. CSV format: RecNo,Label,String (Excel compatible)
+
+CSV IMPORT FORMAT
+-----------------
+RecNo,Label,String
+1,LABEL_NAME,Display Text Here
+2,ANOTHER_LABEL,Another Text Here
+
+TROUBLESHOOTING
+---------------
+- If the app doesn't start, install .NET Framework 4.7.2
+- Ensure all DLL files are in the same folder
+- Run as Administrator if file access issues occur
+
+VERSION HISTORY
+========================================
+Build Date: August 26, 2025
+Build Version: v1.9.4
+Original Author: xfileFIN (eventHorizon) © 2022
+Enhanced by: Derek W © 2025
+Built with: Visual Studio 2022, MSBuild 17.13
diff --git a/GT.RText-Release/app.ico b/GT.RText-Release/app.ico
new file mode 100644
index 0000000..0d17c9a
Binary files /dev/null and b/GT.RText-Release/app.ico differ
diff --git a/GT.RText.sln b/GT.RText.sln
index bebc187..7372ea2 100644
--- a/GT.RText.sln
+++ b/GT.RText.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.4.33103.184
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GT.RText", "GT.RText\GT.RText.csproj", "{9E8B67DF-F7CD-458C-848B-2B148466224E}"
EndProject
@@ -14,9 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GT.NDB0.Core", "GT.NDB0\GT.NDB0.Core.csproj", "{EE1ACABD-0580-41F9-A56D-2676F2E6D608}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GT.RText.Comparer", "GT.RText.Comparer\GT.RText.Comparer.csproj", "{B4F8A315-F0EA-4B86-AE62-681AA5CD873E}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GT.NDB0.Core", "GT.NDB0\GT.NDB0.Core.csproj", "{EE1ACABD-0580-41F9-A56D-2676F2E6D608}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -40,10 +38,6 @@ Global
{EE1ACABD-0580-41F9-A56D-2676F2E6D608}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE1ACABD-0580-41F9-A56D-2676F2E6D608}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE1ACABD-0580-41F9-A56D-2676F2E6D608}.Release|Any CPU.Build.0 = Release|Any CPU
- {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/GT.RText/DarkTheme.cs b/GT.RText/DarkTheme.cs
new file mode 100644
index 0000000..064a29a
--- /dev/null
+++ b/GT.RText/DarkTheme.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace GT.RText
+{
+ ///
+ /// Class to apply dark theme to Windows Forms controls
+ ///
+ public static class DarkTheme
+ {
+ // Dark theme colors
+ public static readonly Color DarkBackground = Color.Black;
+ public static readonly Color DarkSecondaryBackground = Color.FromArgb(20, 20, 20);
+ public static readonly Color DarkControlBackground = Color.FromArgb(15, 15, 15);
+ public static readonly Color DarkForeground = Color.White;
+ public static readonly Color DarkBorder = Color.FromArgb(40, 40, 40);
+ public static readonly Color DarkSelection = Color.FromArgb(0, 122, 204);
+ public static readonly Color DarkMenuBackground = Color.FromArgb(20, 20, 20);
+ public static readonly Color DarkMenuForeground = Color.White;
+ public static readonly Color DarkStatusBackground = Color.FromArgb(0, 122, 204);
+
+ ///
+ /// Applies dark theme to the main form and all its controls
+ ///
+ /// Form to be modified
+ public static void ApplyDarkTheme(Form form)
+ {
+ // Configure main form
+ form.BackColor = DarkBackground;
+ form.ForeColor = DarkForeground;
+
+ // Apply theme to all controls recursively
+ ApplyDarkThemeToControls(form.Controls);
+ }
+
+ ///
+ /// Applies dark theme to a collection of controls recursively
+ ///
+ /// Control collection
+ private static void ApplyDarkThemeToControls(Control.ControlCollection controls)
+ {
+ foreach (Control control in controls)
+ {
+ ApplyDarkThemeToControl(control);
+
+ // Apply recursively to child controls
+ if (control.HasChildren)
+ {
+ ApplyDarkThemeToControls(control.Controls);
+ }
+ }
+ }
+
+ ///
+ /// Applies dark theme to a specific control
+ ///
+ /// Control to be modified
+ private static void ApplyDarkThemeToControl(Control control)
+ {
+ try
+ {
+ switch (control)
+ {
+ case MenuStrip menuStrip:
+ ApplyDarkThemeToMenuStrip(menuStrip);
+ break;
+
+ case ContextMenuStrip contextMenu:
+ ApplyDarkThemeToContextMenu(contextMenu);
+ break;
+
+ case StatusStrip statusStrip:
+ ApplyDarkThemeToStatusStrip(statusStrip);
+ break;
+
+ case TabControl tabControl:
+ ApplyDarkThemeToTabControl(tabControl);
+ break;
+
+ case ListView listView:
+ ApplyDarkThemeToListView(listView);
+ break;
+
+ case TextBox textBox:
+ ApplyDarkThemeToTextBox(textBox);
+ break;
+
+ case Button button:
+ ApplyDarkThemeToButton(button);
+ break;
+
+ case Panel panel:
+ ApplyDarkThemeToPanel(panel);
+ break;
+
+ case GroupBox groupBox:
+ ApplyDarkThemeToGroupBox(groupBox);
+ break;
+
+ case Label label:
+ ApplyDarkThemeToLabel(label);
+ break;
+
+ default:
+ // Apply basic theme for non-specific controls
+ control.BackColor = DarkBackground;
+ control.ForeColor = DarkForeground;
+ break;
+ }
+ }
+ catch (Exception)
+ {
+ // Ignore errors from controls that don't support color changes
+ }
+ }
+
+ private static void ApplyDarkThemeToMenuStrip(MenuStrip menuStrip)
+ {
+ menuStrip.BackColor = DarkMenuBackground;
+ menuStrip.ForeColor = DarkMenuForeground;
+ menuStrip.Renderer = new DarkMenuRenderer();
+
+ foreach (ToolStripItem item in menuStrip.Items)
+ {
+ ApplyDarkThemeToMenuItem(item);
+ }
+ }
+
+ private static void ApplyDarkThemeToContextMenu(ContextMenuStrip contextMenu)
+ {
+ contextMenu.BackColor = DarkMenuBackground;
+ contextMenu.ForeColor = DarkMenuForeground;
+ contextMenu.Renderer = new DarkMenuRenderer();
+
+ foreach (ToolStripItem item in contextMenu.Items)
+ {
+ ApplyDarkThemeToMenuItem(item);
+ }
+ }
+
+ private static void ApplyDarkThemeToMenuItem(ToolStripItem item)
+ {
+ item.BackColor = DarkMenuBackground;
+ item.ForeColor = DarkMenuForeground;
+
+ if (item is ToolStripMenuItem menuItem && menuItem.HasDropDownItems)
+ {
+ foreach (ToolStripItem subItem in menuItem.DropDownItems)
+ {
+ ApplyDarkThemeToMenuItem(subItem);
+ }
+ }
+ }
+
+ private static void ApplyDarkThemeToStatusStrip(StatusStrip statusStrip)
+ {
+ statusStrip.BackColor = DarkSecondaryBackground;
+ statusStrip.ForeColor = DarkForeground;
+ statusStrip.Renderer = new DarkStatusStripRenderer();
+
+ foreach (ToolStripItem item in statusStrip.Items)
+ {
+ item.BackColor = DarkSecondaryBackground;
+ item.ForeColor = DarkForeground;
+ }
+ }
+
+ private static void ApplyDarkThemeToTabControl(TabControl tabControl)
+ {
+ tabControl.BackColor = DarkBackground;
+ tabControl.ForeColor = DarkForeground;
+
+ foreach (TabPage tabPage in tabControl.TabPages)
+ {
+ tabPage.BackColor = DarkBackground;
+ tabPage.ForeColor = DarkForeground;
+ }
+ }
+
+ private static void ApplyDarkThemeToListView(ListView listView)
+ {
+ listView.BackColor = DarkControlBackground;
+ listView.ForeColor = DarkForeground;
+ listView.BorderStyle = BorderStyle.FixedSingle;
+ }
+
+ private static void ApplyDarkThemeToTextBox(TextBox textBox)
+ {
+ textBox.BackColor = DarkControlBackground;
+ textBox.ForeColor = DarkForeground;
+ textBox.BorderStyle = BorderStyle.FixedSingle;
+ }
+
+ private static void ApplyDarkThemeToButton(Button button)
+ {
+ button.BackColor = DarkSecondaryBackground;
+ button.ForeColor = DarkForeground;
+ button.FlatStyle = FlatStyle.Flat;
+ button.FlatAppearance.BorderColor = DarkBorder;
+ button.FlatAppearance.MouseOverBackColor = DarkSelection;
+ }
+
+ private static void ApplyDarkThemeToPanel(Panel panel)
+ {
+ panel.BackColor = DarkBackground;
+ panel.ForeColor = DarkForeground;
+ }
+
+ private static void ApplyDarkThemeToGroupBox(GroupBox groupBox)
+ {
+ groupBox.BackColor = DarkBackground;
+ groupBox.ForeColor = DarkForeground;
+ }
+
+ private static void ApplyDarkThemeToLabel(Label label)
+ {
+ label.BackColor = Color.Transparent;
+ label.ForeColor = DarkForeground;
+ }
+ }
+
+ ///
+ /// Renderer personalizado para menus em tema escuro
+ ///
+ public class DarkMenuRenderer : ToolStripProfessionalRenderer
+ {
+ public DarkMenuRenderer() : base(new DarkMenuColorTable()) { }
+ }
+
+ ///
+ /// Renderer personalizado para status strip em tema escuro
+ ///
+ public class DarkStatusStripRenderer : ToolStripProfessionalRenderer
+ {
+ public DarkStatusStripRenderer() : base(new DarkMenuColorTable()) { }
+ }
+
+ ///
+ /// Tabela de cores personalizada para menus em tema escuro
+ ///
+ public class DarkMenuColorTable : ProfessionalColorTable
+ {
+ public override Color MenuItemSelected => DarkTheme.DarkSelection;
+ public override Color MenuItemSelectedGradientBegin => DarkTheme.DarkSelection;
+ public override Color MenuItemSelectedGradientEnd => DarkTheme.DarkSelection;
+ public override Color MenuItemPressedGradientBegin => DarkTheme.DarkSelection;
+ public override Color MenuItemPressedGradientEnd => DarkTheme.DarkSelection;
+ public override Color MenuItemBorder => DarkTheme.DarkBorder;
+ public override Color MenuBorder => DarkTheme.DarkBorder;
+ public override Color MenuStripGradientBegin => DarkTheme.DarkMenuBackground;
+ public override Color MenuStripGradientEnd => DarkTheme.DarkMenuBackground;
+ public override Color ToolStripDropDownBackground => DarkTheme.DarkMenuBackground;
+ public override Color ImageMarginGradientBegin => DarkTheme.DarkMenuBackground;
+ public override Color ImageMarginGradientMiddle => DarkTheme.DarkMenuBackground;
+ public override Color ImageMarginGradientEnd => DarkTheme.DarkMenuBackground;
+ public override Color SeparatorDark => DarkTheme.DarkBorder;
+ public override Color SeparatorLight => DarkTheme.DarkBorder;
+ }
+}
diff --git a/GT.RText/GT.RText.csproj b/GT.RText/GT.RText.csproj
index a9f3c28..62fd18a 100644
--- a/GT.RText/GT.RText.csproj
+++ b/GT.RText/GT.RText.csproj
@@ -35,16 +35,7 @@
4
-
- ..\packages\Costura.Fody.3.3.3\lib\net40\Costura.dll
-
-
- ..\packages\Microsoft-WindowsAPICodePack-Core.1.1.4\lib\net472\Microsoft.WindowsAPICodePack.dll
-
-
- ..\packages\Microsoft-WindowsAPICodePack-Shell.1.1.4\lib\net472\Microsoft.WindowsAPICodePack.Shell.dll
-
@@ -63,6 +54,7 @@
+
@@ -107,6 +99,9 @@
+
+ Always
+
diff --git a/GT.RText/Main.Designer.cs b/GT.RText/Main.Designer.cs
index d5aff52..335d222 100644
--- a/GT.RText/Main.Designer.cs
+++ b/GT.RText/Main.Designer.cs
@@ -34,6 +34,7 @@ private void InitializeComponent() {
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.editToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
this.addEditFromCSVFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.importExcelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.contextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.addToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -116,7 +117,8 @@ private void InitializeComponent() {
// editToolStripMenuItem1
//
this.editToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.addEditFromCSVFileToolStripMenuItem});
+ this.addEditFromCSVFileToolStripMenuItem,
+ this.importExcelToolStripMenuItem});
this.editToolStripMenuItem1.Name = "editToolStripMenuItem1";
this.editToolStripMenuItem1.Size = new System.Drawing.Size(39, 20);
this.editToolStripMenuItem1.Text = "Edit";
@@ -128,6 +130,13 @@ private void InitializeComponent() {
this.addEditFromCSVFileToolStripMenuItem.Text = "Add/Edit Current Category from CSV File (Key, Value)";
this.addEditFromCSVFileToolStripMenuItem.Click += new System.EventHandler(this.addEditFromCSVFileToolStripMenuItem_Click);
//
+ // importExcelToolStripMenuItem
+ //
+ this.importExcelToolStripMenuItem.Name = "importExcelToolStripMenuItem";
+ this.importExcelToolStripMenuItem.Size = new System.Drawing.Size(353, 22);
+ this.importExcelToolStripMenuItem.Text = "Import CSV for Current Category (RecNo, Label, String)";
+ this.importExcelToolStripMenuItem.Click += new System.EventHandler(this.importExcelToolStripMenuItem_Click);
+ //
// contextMenuStrip
//
this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -239,7 +248,7 @@ private void InitializeComponent() {
this.Controls.Add(this.menuStrip);
this.MainMenuStrip = this.menuStrip;
this.Name = "Main";
- this.Text = "RT2 Editor by xfileFIN";
+ this.Text = "GT.R Text Editor (Modified by Derek W 🏴☠️)";
this.SizeChanged += new System.EventHandler(this.Main_SizeChanged);
this.menuStrip.ResumeLayout(false);
this.menuStrip.PerformLayout();
@@ -274,6 +283,7 @@ private void InitializeComponent() {
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem1;
private System.Windows.Forms.ToolStripMenuItem addEditFromCSVFileToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem importExcelToolStripMenuItem;
private System.Windows.Forms.OpenFileDialog csvOpenFileDialog;
}
}
diff --git a/GT.RText/Main.cs b/GT.RText/Main.cs
index d66b795..9cb3bf1 100644
--- a/GT.RText/Main.cs
+++ b/GT.RText/Main.cs
@@ -1,21 +1,43 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.IO;
using System.Windows.Forms;
+using System.Runtime.InteropServices;
// Required for the non crappy folder picker
// https://stackoverflow.com/q/11624298
-using Microsoft.WindowsAPICodePack.Dialogs;
+// using Microsoft.WindowsAPICodePack.Dialogs;
using GT.RText.Core;
using GT.RText.Core.Exceptions;
using GT.Shared.Logging;
+using GT.Shared;
using System.Linq;
namespace GT.RText
{
public partial class Main : Form
{
+ // Windows API declarations for dark title bar
+ [DllImport("dwmapi.dll", PreserveSig = true)]
+ private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct MARGINS
+ {
+ public int leftWidth;
+ public int rightWidth;
+ public int topHeight;
+ public int bottomHeight;
+ }
+
+ private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
+ private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
+
///
/// Designates whether the currently loaded content is a project folder.
///
@@ -33,6 +55,7 @@ public partial class Main : Form
private List _rTexts;
private ListViewColumnSorter _columnSorter;
+ private ContextMenuStrip _categoriesContextMenu;
public RTextParser CurrentRText => _rTexts[tabControlLocalFiles.SelectedIndex];
public RTextPageBase CurrentPage { get; set; }
@@ -47,6 +70,54 @@ public Main()
_columnSorter = new ListViewColumnSorter();
this.listViewEntries.ListViewItemSorter = _columnSorter;
this.listViewEntries.Sorting = SortOrder.Ascending;
+
+ InitializeExcelImportFeature();
+
+ // Apply dark theme
+ DarkTheme.ApplyDarkTheme(this);
+
+ // Apply dark title bar
+ this.Load += (s, e) => ApplyDarkTitleBar();
+
+ // Load icon if exists
+ try
+ {
+ string iconPath = Path.Combine(Application.StartupPath, "app.ico");
+ if (File.Exists(iconPath))
+ {
+ this.Icon = new Icon(iconPath);
+ }
+ }
+ catch
+ {
+ // Ignore if unable to load the icon
+ }
+ }
+
+ ///
+ /// Applies dark title bar using Windows API
+ ///
+ private void ApplyDarkTitleBar()
+ {
+ try
+ {
+ if (this.Handle != IntPtr.Zero)
+ {
+ int value = 1;
+ // Try first the newest API version
+ int result = DwmSetWindowAttribute(this.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, ref value, sizeof(int));
+
+ // If it fails, try the older version
+ if (result != 0)
+ {
+ DwmSetWindowAttribute(this.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, ref value, sizeof(int));
+ }
+ }
+ }
+ catch
+ {
+ // Ignore if API is not available
+ }
}
#region Events
@@ -71,13 +142,11 @@ private void openToolStripMenuItem_Click(object sender, EventArgs e)
private void openFolderToolStripMenuItem_Click(object sender, EventArgs e)
{
- var dialog = new CommonOpenFileDialog();
- dialog.EnsureFileExists = true;
- dialog.EnsurePathExists = true;
-
- dialog.IsFolderPicker = true;
+ var dialog = new FolderBrowserDialog();
+ dialog.Description = "Selecione a pasta com os arquivos RT";
+ dialog.ShowNewFolderButton = false;
- if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;
+ if (dialog.ShowDialog() != DialogResult.OK) return;
_rTexts.Clear();
@@ -87,7 +156,7 @@ private void openFolderToolStripMenuItem_Click(object sender, EventArgs e)
ClearTabs();
bool firstTab = true;
- string[] files = Directory.GetFiles(dialog.FileName, "*", SearchOption.TopDirectoryOnly);
+ string[] files = Directory.GetFiles(dialog.SelectedPath, "*", SearchOption.TopDirectoryOnly);
if (files.Any(f => RTextParser.Locales.ContainsKey(Path.GetFileNameWithoutExtension(f))))
{
@@ -118,7 +187,7 @@ private void openFolderToolStripMenuItem_Click(object sender, EventArgs e)
else
{
// Locale files are located per-UI project, in their own folder (i.e arcade/US/rtext.rt2)
- string[] folders = Directory.GetDirectories(dialog.FileName, "*", SearchOption.TopDirectoryOnly);
+ string[] folders = Directory.GetDirectories(dialog.SelectedPath, "*", SearchOption.TopDirectoryOnly);
foreach (var folder in folders)
{
string actualDirName = Path.GetFileName(folder);
@@ -152,24 +221,22 @@ private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_isUiFolderProject)
{
- var dialog = new CommonOpenFileDialog();
- dialog.EnsureFileExists = true;
- dialog.EnsurePathExists = true;
+ var dialog = new FolderBrowserDialog();
+ dialog.Description = "Selecione a pasta para salvar os arquivos";
+ dialog.ShowNewFolderButton = true;
- dialog.IsFolderPicker = true;
-
- if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;
+ if (dialog.ShowDialog() != DialogResult.OK) return;
foreach (var rtext in _rTexts)
{
if (_isGT6AndAboveProjectStyle)
{
- string localePath = Path.Combine(dialog.FileName, $"{rtext.LocaleCode}.rt2");
+ string localePath = Path.Combine(dialog.SelectedPath, $"{rtext.LocaleCode}.rt2");
rtext.RText.Save(localePath);
}
else
{
- string localePath = Path.Combine(dialog.FileName, rtext.LocaleCode);
+ string localePath = Path.Combine(dialog.SelectedPath, rtext.LocaleCode);
Directory.CreateDirectory(localePath);
rtext.RText.Save(Path.Combine(localePath, "rtext.rt2"));
@@ -485,6 +552,19 @@ private void addEditFromCSVFileToolStripMenuItem_Click(object sender, EventArgs
DisplayEntries(CurrentPage);
}
+
+ private void importExcelToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ if (!_rTexts.Any() || CurrentRText is null || CurrentPage is null)
+ {
+ MessageBox.Show("No file loaded or category selected.", "Warning",
+ MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ ImportExcelForCategory_Click(sender, e);
+ }
+
#endregion
private void ClearTabs()
@@ -618,5 +698,311 @@ private void SortEntriesListView(int columnIndex)
// Perform the sort with these new sort options.
this.listViewEntries.Sort();
}
+
+ #region Excel Import Feature
+
+ private void InitializeExcelImportFeature()
+ {
+ // Criar menu de contexto para as categorias
+ _categoriesContextMenu = new ContextMenuStrip();
+
+ var importCsvItem = new ToolStripMenuItem("Import CSV to this category");
+ importCsvItem.Click += ImportExcelForCategory_Click;
+ importCsvItem.Image = null; // Pode adicionar um ícone se desejar
+
+ var exportCsvItem = new ToolStripMenuItem("Export this category to CSV");
+ exportCsvItem.Click += ExportCategoryToCsv_Click;
+ exportCsvItem.Image = null; // Pode adicionar um ícone se desejar
+
+ var createSampleItem = new ToolStripMenuItem("Create a CSV in the correct format");
+ createSampleItem.Click += CreateSampleCsv_Click;
+
+ _categoriesContextMenu.Items.Add(importCsvItem);
+ _categoriesContextMenu.Items.Add(exportCsvItem);
+ _categoriesContextMenu.Items.Add(new ToolStripSeparator());
+ _categoriesContextMenu.Items.Add(createSampleItem);
+
+ // Associar o menu de contexto ao ListView de páginas/categorias
+ listViewPages.ContextMenuStrip = _categoriesContextMenu;
+ }
+
+ private void CreateSampleCsv_Click(object sender, EventArgs e)
+ {
+ var saveFileDialog = new SaveFileDialog
+ {
+ Filter = "CSV Files (*.csv)|*.csv",
+ Title = "Save sample CSV file",
+ FileName = "sample_import.csv"
+ };
+
+ if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
+ {
+ try
+ {
+ ExcelImporter.CreateSampleCsv(saveFileDialog.FileName);
+ MessageBox.Show($"Sample file created successfully!\n\nLocation: {saveFileDialog.FileName}\n\n" +
+ "You can edit this file and use it to import data.",
+ "File created", MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+ toolStripStatusLabel.Text = "Sample CSV file created successfully";
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Error creating sample file: {ex.Message}", "Error",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+
+ private void ImportExcelForCategory_Click(object sender, EventArgs e)
+ {
+ // Verificar se há uma categoria selecionada
+ if (listViewPages.SelectedItems.Count <= 0 || listViewPages.SelectedItems[0] == null)
+ {
+ MessageBox.Show("Please select a category first.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ var selectedItem = listViewPages.SelectedItems[0];
+ var page = (RTextPageBase)selectedItem.Tag;
+ var categoryName = page.Name;
+
+ var openFileDialog = new OpenFileDialog
+ {
+ Filter = "CSV Files (*.csv)|*.csv|All files (*.*)|*.*",
+ Title = "Select CSV file for import",
+ CheckFileExists = true,
+ CheckPathExists = true
+ };
+
+ if (openFileDialog.ShowDialog(this) == DialogResult.OK)
+ {
+ ImportCsvData(openFileDialog.FileName, page, categoryName);
+ }
+ }
+
+ private void ImportCsvData(string filePath, RTextPageBase page, string categoryName)
+ {
+ try
+ {
+ // Mostrar cursor de espera
+ this.Cursor = Cursors.WaitCursor;
+ toolStripStatusLabel.Text = "Importing CSV data...";
+
+ var importResult = ExcelImporter.ImportFromCsv(filePath);
+
+ if (!importResult.Success)
+ {
+ MessageBox.Show($"Import error: {importResult.Message}", "Error",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+
+ // Mostrar prévia e confirmar importação
+ var previewMessage = $"File: {Path.GetFileName(filePath)}\n" +
+ $"Category: {categoryName}\n" +
+ $"Records found: {importResult.ImportedEntries.Count}\n\n" +
+ "First records:\n";
+
+ // Mostrar os primeiros 5 registros como prévia
+ var preview = importResult.ImportedEntries.Take(5);
+ foreach (var entry in preview)
+ {
+ var truncatedString = entry.String.Length > 50 ? entry.String.Substring(0, 50) + "..." : entry.String;
+ previewMessage += $"• {entry.RecNo} | {entry.Label} | {truncatedString}\n";
+ }
+
+ if (importResult.ImportedEntries.Count > 5)
+ {
+ previewMessage += $"... and {importResult.ImportedEntries.Count - 5} more records.\n";
+ }
+
+ previewMessage += "\nThis operation will:\n" +
+ "• Replace existing texts with same Label\n" +
+ "• Add new records if Label doesn't exist\n" +
+ "• Keep existing records not in the file\n\n" +
+ "Do you want to continue with the import?";
+
+ var confirmResult = MessageBox.Show(previewMessage, "Confirm Import",
+ MessageBoxButtons.YesNo, MessageBoxIcon.Question);
+
+ if (confirmResult == DialogResult.Yes)
+ {
+ ApplyImportedData(importResult.ImportedEntries, page, categoryName);
+
+ MessageBox.Show($"Import completed successfully!\n\n{importResult.Message}",
+ "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+ // Atualizar a interface
+ DisplayEntries(page);
+ toolStripStatusLabel.Text = $"Import completed: {importResult.ImportedEntries.Count} records processed";
+ }
+ else
+ {
+ toolStripStatusLabel.Text = "Import cancelled by user";
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Error during import: {ex.Message}", "Error",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ toolStripStatusLabel.Text = "Error during import";
+ }
+ finally
+ {
+ this.Cursor = Cursors.Default;
+ }
+ }
+
+ private void ApplyImportedData(List entries, RTextPageBase page, string categoryName)
+ {
+ int updatedCount = 0;
+ int addedCount = 0;
+
+ foreach (var entry in entries)
+ {
+ if (_isUiFolderProject)
+ {
+ // Apply to all locales if it's a folder project
+ foreach (var rt in _rTexts)
+ {
+ try
+ {
+ var rtPage = rt.RText.GetPages()[categoryName];
+ if (rtPage.PairExists(entry.Label))
+ {
+ // For existing records, preserve original ID and only update the value
+ var existingPair = rtPage.PairUnits[entry.Label];
+ rtPage.EditRow(existingPair.ID, entry.Label, entry.String);
+ updatedCount++;
+ }
+ else
+ {
+ // For new records, use a unique ID based on last ID + 1
+ int newId = rtPage.PairUnits.Count > 0 ? rtPage.GetLastId() + 1 : 1;
+ rtPage.AddRow(newId, entry.Label, entry.String);
+ addedCount++;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error applying data to locale {rt.LocaleCode}: {ex.Message}");
+ }
+ }
+ }
+ else
+ {
+ // Apply only to current file
+ if (page.PairExists(entry.Label))
+ {
+ // For existing records, preserve original ID and only update the value
+ var existingPair = page.PairUnits[entry.Label];
+ page.EditRow(existingPair.ID, entry.Label, entry.String);
+ updatedCount++;
+ }
+ else
+ {
+ // For new records, use a unique ID based on last ID + 1
+ int newId = page.PairUnits.Count > 0 ? page.GetLastId() + 1 : 1;
+ page.AddRow(newId, entry.Label, entry.String);
+ addedCount++;
+ }
+ }
+ }
+
+ var summary = "";
+ if (_isUiFolderProject)
+ {
+ summary = $"Aplicado para {_rTexts.Count} locales: ";
+ }
+
+ summary += $"{updatedCount} registros atualizados, {addedCount} registros adicionados";
+
+ Console.WriteLine($"Importação completada: {summary}");
+ }
+
+ private void ExportCategoryToCsv_Click(object sender, EventArgs e)
+ {
+ // Verificar se há uma categoria selecionada
+ if (listViewPages.SelectedItems.Count <= 0 || listViewPages.SelectedItems[0] == null)
+ {
+ MessageBox.Show("Please select a category first.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ var selectedItem = listViewPages.SelectedItems[0];
+ var page = (RTextPageBase)selectedItem.Tag;
+ var categoryName = page.Name;
+
+ var saveFileDialog = new SaveFileDialog
+ {
+ Filter = "CSV Files (*.csv)|*.csv|All files (*.*)|*.*",
+ Title = "Export category to CSV",
+ FileName = $"{categoryName}_export.csv",
+ CheckPathExists = true
+ };
+
+ if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
+ {
+ ExportCategoryData(saveFileDialog.FileName, page, categoryName);
+ }
+ }
+
+ private void ExportCategoryData(string filePath, RTextPageBase page, string categoryName)
+ {
+ try
+ {
+ // Mostrar cursor de espera
+ this.Cursor = Cursors.WaitCursor;
+ toolStripStatusLabel.Text = "Exporting category data...";
+
+ // Converter os dados da página para o formato de exportação
+ var exportEntries = new List();
+
+ foreach (var pair in page.PairUnits.Values.OrderBy(p => p.ID))
+ {
+ exportEntries.Add(new ImportedEntry
+ {
+ RecNo = pair.ID,
+ Label = pair.Label,
+ String = pair.Value
+ });
+ }
+
+ // Exportar para CSV
+ bool success = ExcelImporter.ExportToCsv(filePath, exportEntries);
+
+ if (success)
+ {
+ var successMessage = $"Category '{categoryName}' exported successfully!\n\n" +
+ $"File: {Path.GetFileName(filePath)}\n" +
+ $"Records exported: {exportEntries.Count}\n" +
+ $"Format: RecNo,Label,String";
+
+ MessageBox.Show(successMessage, "Export Successful",
+ MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+ toolStripStatusLabel.Text = $"Export completed: {exportEntries.Count} records exported to {Path.GetFileName(filePath)}";
+ }
+ else
+ {
+ MessageBox.Show("Failed to export category data. Please check the file path and try again.",
+ "Export Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ toolStripStatusLabel.Text = "Export failed";
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Error during export: {ex.Message}", "Error",
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ toolStripStatusLabel.Text = "Error during export";
+ }
+ finally
+ {
+ this.Cursor = Cursors.Default;
+ }
+ }
+
+ #endregion
}
}
diff --git a/GT.RText/Properties/AssemblyInfo.cs b/GT.RText/Properties/AssemblyInfo.cs
index 003ad58..fcc2454 100644
--- a/GT.RText/Properties/AssemblyInfo.cs
+++ b/GT.RText/Properties/AssemblyInfo.cs
@@ -9,8 +9,8 @@
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("eventHorizon")]
-[assembly: AssemblyProduct("GT.RText")]
-[assembly: AssemblyCopyright("Copyright © xfileFIN 2022")]
+[assembly: AssemblyProduct("GT.RText Enhanced Edition")]
+[assembly: AssemblyCopyright("Original © xfileFIN 2022 | Enhanced by Derek W 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.9.3.0")]
-[assembly: AssemblyFileVersion("1.9.3.0")]
+[assembly: AssemblyVersion("1.9.4.0")]
+[assembly: AssemblyFileVersion("1.9.4.0")]
diff --git a/GT.RText/RowEditor.Designer.cs b/GT.RText/RowEditor.Designer.cs
index 3ca60c7..90e9b9a 100644
--- a/GT.RText/RowEditor.Designer.cs
+++ b/GT.RText/RowEditor.Designer.cs
@@ -141,7 +141,7 @@ private void InitializeComponent()
this.Controls.Add(this.label_id);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
this.Name = "RowEditor";
- this.Text = "Editor";
+ this.Text = "GT.RText - Row Editor";
((System.ComponentModel.ISupportInitialize)(this.numericUpDown_id)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
diff --git a/GT.RText/RowEditor.cs b/GT.RText/RowEditor.cs
index ab62ff1..9758d54 100644
--- a/GT.RText/RowEditor.cs
+++ b/GT.RText/RowEditor.cs
@@ -1,4 +1,6 @@
using System;
+using System.Drawing;
+using System.IO;
using System.Windows.Forms;
namespace GT.RText
@@ -15,6 +17,12 @@ public partial class RowEditor : Form
public RowEditor()
{
InitializeComponent();
+
+ // Aplicar tema escuro
+ DarkTheme.ApplyDarkTheme(this);
+
+ // Carregar ícone se existir
+ LoadIcon();
}
public RowEditor(bool isWithoutId, bool isUiProject)
@@ -26,6 +34,12 @@ public RowEditor(bool isWithoutId, bool isUiProject)
_isUiProject = isUiProject;
if (isUiProject)
applyAllLocalesCheckBox.Visible = true;
+
+ // Aplicar tema escuro
+ DarkTheme.ApplyDarkTheme(this);
+
+ // Carregar ícone se existir
+ LoadIcon();
}
public RowEditor(int id, string label, string data, bool isUiProject)
@@ -44,6 +58,28 @@ public RowEditor(int id, string label, string data, bool isUiProject)
richTextBox_data.Text = data;
HandleId(id);
+
+ // Aplicar tema escuro
+ DarkTheme.ApplyDarkTheme(this);
+
+ // Carregar ícone se existir
+ LoadIcon();
+ }
+
+ private void LoadIcon()
+ {
+ try
+ {
+ string iconPath = Path.Combine(Application.StartupPath, "app.ico");
+ if (File.Exists(iconPath))
+ {
+ this.Icon = new Icon(iconPath);
+ }
+ }
+ catch
+ {
+ // Ignorar se não conseguir carregar o ícone
+ }
}
private void HandleId(int id)
diff --git a/GT.RText/app.ico b/GT.RText/app.ico
new file mode 100644
index 0000000..0d17c9a
Binary files /dev/null and b/GT.RText/app.ico differ
diff --git a/GT.RText/icon.txt b/GT.RText/icon.txt
new file mode 100644
index 0000000..4380b8f
--- /dev/null
+++ b/GT.RText/icon.txt
@@ -0,0 +1 @@
+AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/GT.RText/packages.config b/GT.RText/packages.config
index aaf6587..902666d 100644
--- a/GT.RText/packages.config
+++ b/GT.RText/packages.config
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/GT.Shared/ExcelImporter.cs b/GT.Shared/ExcelImporter.cs
new file mode 100644
index 0000000..83bd80e
--- /dev/null
+++ b/GT.Shared/ExcelImporter.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace GT.Shared
+{
+ public class ExcelImportResult
+ {
+ public bool Success { get; set; }
+ public string Message { get; set; }
+ public List ImportedEntries { get; set; } = new List();
+ }
+
+ public class ImportedEntry
+ {
+ public int RecNo { get; set; }
+ public string Label { get; set; }
+ public string String { get; set; }
+ }
+
+ public static class ExcelImporter
+ {
+ ///
+ /// Imports data from a CSV file with format: RecNo, Label, String
+ ///
+ public static ExcelImportResult ImportFromCsv(string filePath)
+ {
+ var result = new ExcelImportResult();
+
+ try
+ {
+ if (!File.Exists(filePath))
+ {
+ result.Message = "Arquivo não encontrado.";
+ return result;
+ }
+
+ var lines = File.ReadAllLines(filePath);
+
+ if (lines.Length < 2)
+ {
+ result.Message = "The file must contain at least one header line and one data line.";
+ return result;
+ }
+
+ // Check header (first line)
+ var headerLine = lines[0].ToLower();
+ if (!headerLine.Contains("recno") || !headerLine.Contains("label") || !headerLine.Contains("string"))
+ {
+ result.Message = "The file must have columns in order: RecNo, Label, String.\n" +
+ $"Header found: '{lines[0]}'";
+ return result;
+ }
+
+ // Process data lines (skip header)
+ for (int i = 1; i < lines.Length; i++)
+ {
+ var line = lines[i].Trim();
+
+ // Pular linhas vazias
+ if (string.IsNullOrEmpty(line))
+ continue;
+
+ var parts = ParseCsvLine(line);
+
+ if (parts.Length < 3)
+ {
+ Console.WriteLine($"Linha {i + 1}: Formato inválido '{line}', ignorando linha.");
+ continue;
+ }
+
+ // Clean and process fields
+ var recNoText = parts[0].Trim().Trim('"'); // Remove extra quotes from Excel
+ var label = parts[1].Trim().Trim('"'); // Remove extra quotes from Excel
+ var stringValue = parts[2].Trim().Trim('"'); // Remove extra quotes from Excel
+
+ // If there are still quotes in the middle of text, restore them correctly
+ label = label.Replace("\"\"", "\"");
+ stringValue = stringValue.Replace("\"\"", "\"");
+
+ if (int.TryParse(recNoText, out int recNo))
+ {
+ result.ImportedEntries.Add(new ImportedEntry
+ {
+ RecNo = recNo,
+ Label = label,
+ String = stringValue
+ });
+ }
+ else
+ {
+ Console.WriteLine($"Linha {i + 1}: RecNo inválido '{recNoText}', ignorando linha.");
+ }
+ }
+
+ if (result.ImportedEntries.Count == 0)
+ {
+ result.Message = "Nenhum registro válido foi encontrado no arquivo.";
+ return result;
+ }
+
+ result.Success = true;
+ result.Message = $"Importados {result.ImportedEntries.Count} registros com sucesso.";
+ }
+ catch (Exception ex)
+ {
+ result.Success = false;
+ result.Message = $"Erro ao importar arquivo: {ex.Message}";
+ }
+
+ return result;
+ }
+
+ ///
+ /// Parses a CSV line considering quotes and commas inside fields
+ /// Compatible with Excel format that adds quotes to all fields
+ ///
+ private static string[] ParseCsvLine(string line)
+ {
+ var result = new List();
+ var current = "";
+ bool inQuotes = false;
+
+ for (int i = 0; i < line.Length; i++)
+ {
+ char c = line[i];
+
+ if (c == '"')
+ {
+ if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
+ {
+ // Two consecutive quotes = one literal quote
+ current += '"';
+ i++; // Skip next quote
+ }
+ else
+ {
+ // Toggle quote state
+ inQuotes = !inQuotes;
+ }
+ }
+ else if (c == ',' && !inQuotes)
+ {
+ // Comma outside quotes = field separator
+ result.Add(current.Trim('"')); // Remove aspas extras do Excel
+ current = "";
+ }
+ else
+ {
+ current += c;
+ }
+ }
+
+ // Add last field, removing extra quotes
+ result.Add(current.Trim('"'));
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Creates a sample CSV file for the user
+ ///
+ public static void CreateSampleCsv(string filePath)
+ {
+ var sampleContent = @"RecNo,Label,String
+1,the exact same label must be here,This is an example string for entry 1.
+2,the exact same label must be here,This is an example string for entry 2.";
+
+ File.WriteAllText(filePath, sampleContent);
+ }
+
+ ///
+ /// Exports category entries to CSV format
+ ///
+ public static bool ExportToCsv(string filePath, List entries)
+ {
+ try
+ {
+ var csvContent = "RecNo,Label,String\n";
+
+ foreach (var entry in entries)
+ {
+ // Escape quotes in the data by doubling them
+ var label = entry.Label.Replace("\"", "\"\"");
+ var stringValue = entry.String.Replace("\"", "\"\"");
+
+ // Add quotes around fields that contain commas, quotes, or newlines
+ if (label.Contains(",") || label.Contains("\"") || label.Contains("\n"))
+ label = $"\"{label}\"";
+
+ if (stringValue.Contains(",") || stringValue.Contains("\"") || stringValue.Contains("\n"))
+ stringValue = $"\"{stringValue}\"";
+
+ csvContent += $"{entry.RecNo},{label},{stringValue}\n";
+ }
+
+ File.WriteAllText(filePath, csvContent);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/README.md b/README.md
index 87db355..90ec990 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,86 @@
-# GT.RText
-Supports editing RT03, RT04, RT05 and 50TR files.
+# GT.RText v1.9.4 - Enhanced Edition
+====================================
-
+
-### Credits:
-[Nenkai](https://github.com/Nenkai)
+## IMPORTANT NOTICE
+This is a **MODIFIED** version of the original GT.RText by xfileFIN
+Original author: **xfileFIN** © 2022
+Enhanced by: **Derek W** after requested by a friend - 2025
+This version includes additional features not present in the original.
-- solving which cipher was being called to decipher the RT05 and 50TR strings.
-- Adding support for editing whole UI folder
+---
-[flatz](https://github.com/flatz/gttool)
+## OVERVIEW
+Gran Turismo RText Editor with enhanced features, including:
+- CSV Import/Export functionality for bulk editing
+- Pure black dark theme with dark title bar
+- Custom icon support
+- English interface and improved usability
-- for reverse engineering the said cipher (Salsa20).
+---
+
+## CREDITS
+• Original GT.RText: xfileFIN - 2022
+• Enhanced Edition: Derek W - 2025
+• Additional Features: CSV Import/Export, Dark Theme, UI Improvements
+
+---
+
+## SYSTEM REQUIREMENTS
+- Windows 10/11
+- .NET Framework 4.7.2 or higher
+- 50MB free disk space
+
+---
+
+## INSTALLATION
+1. Extract all files to a folder of your choice
+2. Ensure all DLL files are in the same folder as GT.RText.exe
+3. Run GT.RText.exe
+
+---
+
+## FEATURES
+- **Dark Theme:** Pure black interface with dark window title bar
+- **CSV Import:** Import text entries for bulk editing from CSV files
+- **CSV Export:** Export category data to CSV format for backup/editing
+- **Icon Support:** Custom application icon (app.ico)
+- **English Interface:** Fully translated interface and comments
+- **Enhanced Compatibility:** Works with Excel CSV formats
+
+---
+
+## USAGE
+1. Open a Gran Turismo RText file (`.rt03`, `.rt04`, `.rt05`, `._50tr`)
+2. Right-click on a category to access Import/Export options
+3. Use "Import this category from CSV" for bulk updates
+4. Use "Export this category to CSV" for data backup
+5. CSV format: `RecNo,Label,String` (Excel compatible)
+
+---
+
+## CSV IMPORT FORMAT
+```
+RecNo,Label,String
+1,LABEL_NAME,Display Text Here
+2,ANOTHER_LABEL,Another Text Here
+```
+
+---
+
+## TROUBLESHOOTING
+- If the app doesn't start, install .NET Framework 4.7.2
+- Ensure all DLL files are in the same folder
+- Run as Administrator if file access issues occur
+
+---
+
+## VERSION HISTORY
+Build Date: August 26, 2025
+Build Version: v1.9.4
+Original Author: xfileFIN © 2022
+Enhanced by: Derek W © 2025
+Built with: Visual Studio 2022, MSBuild 17.13