From 0bd9bdc950b7429e3d8ff132919c0377d4ba6fd0 Mon Sep 17 00:00:00 2001 From: Mihai Badea Date: Wed, 18 Feb 2026 23:21:07 +0000 Subject: [PATCH 1/5] implement exporting profile to single template. useful for compressing multiple templates into just one uses IPCCharacterProfile similar to how PCP handling uses it to compress to one template --- CustomizePlus/Core/Helpers/Base64Helper.cs | 31 +++++++++++++++ .../MainWindow/Tabs/Profiles/ProfilePanel.cs | 39 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/CustomizePlus/Core/Helpers/Base64Helper.cs b/CustomizePlus/Core/Helpers/Base64Helper.cs index be360e8..8aa8268 100644 --- a/CustomizePlus/Core/Helpers/Base64Helper.cs +++ b/CustomizePlus/Core/Helpers/Base64Helper.cs @@ -1,5 +1,9 @@ using CustomizePlus.Core.Data; using CustomizePlus.Templates.Data; +using CustomizePlus.Api.Data; +using CustomizePlus.Profiles; +using CustomizePlus.Profiles.Data; +using CustomizePlus.Templates; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -50,6 +54,33 @@ public static string ExportTemplateToBase64(Template template) } } + public static string ExportProfileToBase64(Profile profile) + { + // Does the same thing as before above method but turns the profile into a template first via IPCCharacterProfile + // same as is done over in the PCP Service functionality + try + { + var ipcProfile = IPCCharacterProfile.FromFullProfile(profile); + var template = new Template(ipcProfile); + + var json = template.JsonSerialize(); + var bytes = Encoding.UTF8.GetBytes(json.ToString(Formatting.None)); + using var compressedStream = new MemoryStream(); + using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress)) + { + zipStream.WriteByte(Template.Version); + zipStream.Write(bytes, 0, bytes.Length); + } + + return Convert.ToBase64String(compressedStream.ToArray()); + } + + catch + { + return string.Empty; + } + } + // Decompress a base64 encoded string to the given type and a prepended version byte if possible. // On failure, data will be String error and version will be byte.MaxValue. // Original by Ottermandias: OtterGui <3 diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs index 580ac3e..da1e6e2 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Extensions; +using OtterGui.Log; using System; using System.Linq; using System.Numerics; @@ -13,6 +14,7 @@ using CustomizePlus.UI.Windows.Controls; using CustomizePlus.Templates; using CustomizePlus.Core.Data; +using CustomizePlus.Core.Helpers; using CustomizePlus.Templates.Events; using Penumbra.GameData.Actors; using Penumbra.String; @@ -21,6 +23,7 @@ using CustomizePlus.Core.Extensions; using Dalamud.Interface.Components; using OtterGui.Extensions; +using System.Windows.Forms; namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles; @@ -34,6 +37,8 @@ public class ProfilePanel private readonly ActorAssignmentUi _actorAssignmentUi; private readonly ActorManager _actorManager; private readonly TemplateEditorEvent _templateEditorEvent; + private readonly PopupSystem _popupSystem; + private readonly Logger _logger; private string? _newName; private int? _newPriority; @@ -54,7 +59,9 @@ public ProfilePanel( TemplateEditorManager templateEditorManager, ActorAssignmentUi actorAssignmentUi, ActorManager actorManager, - TemplateEditorEvent templateEditorEvent) + TemplateEditorEvent templateEditorEvent, + PopupSystem popupSystem, + Logger logger) { _selector = selector; _manager = manager; @@ -64,6 +71,8 @@ public ProfilePanel( _actorAssignmentUi = actorAssignmentUi; _actorManager = actorManager; _templateEditorEvent = templateEditorEvent; + _popupSystem = popupSystem; + _logger = logger; } public void Draw() @@ -97,9 +106,20 @@ private HeaderDrawer.Button LockButton() OnClick = () => _manager.SetWriteProtection(_selector.Selected!, true) }; + private HeaderDrawer.Button ExportToClipboardButton() + => _selector.Selected == null + ? HeaderDrawer.Button.Invisible + :new HeaderDrawer.Button { + Description = "Copy the current profile to your clipboard.", + Icon = FontAwesomeIcon.Copy, + OnClick = ExportToClipboard, + Visible = _selector.Selected != null, + Disabled = false + }; + private void DrawHeader() => HeaderDrawer.Draw(SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), - 0, LockButton(), + 1, ExportToClipboardButton(), LockButton(), HeaderDrawer.Button.IncognitoButton(_selector.IncognitoMode, v => _selector.IncognitoMode = v)); private void DrawMultiSelection() @@ -248,6 +268,21 @@ private void DrawBasicSettings() } } + private void ExportToClipboard() + { + try + { + var data = Base64Helper.ExportProfileToBase64(_selector.Selected!); + Clipboard.SetText(data); + _popupSystem.ShowPopup(PopupSystem.Messages.ClipboardDataNotLongTerm); + } + catch (Exception ex) + { + _logger.Error($"Could not copy data from profile {_selector.Selected!.UniqueId} to clipboard: {ex}"); + _popupSystem.ShowPopup(PopupSystem.Messages.ActionError); + } + } + private void DrawAddCharactersArea() { using (var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f))) From 899f7c5b5b0e5a1ff09d227502fa6e53177d5a88 Mon Sep 17 00:00:00 2001 From: Mihai Badea Date: Sun, 22 Feb 2026 17:12:22 +0000 Subject: [PATCH 2/5] refactor to utilize OtterGUI ImgGui clipboard function wrapper class instead of calling System.Windows.Forms / ImGui clipboard functions directly --- .../MainWindow/Tabs/Templates/TemplateFileSystemSelector.cs | 2 +- .../UI/Windows/MainWindow/Tabs/Templates/TemplatePanel.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplateFileSystemSelector.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplateFileSystemSelector.cs index 17d94ce..b762e80 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplateFileSystemSelector.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplateFileSystemSelector.cs @@ -315,7 +315,7 @@ private void ClipboardImportButton(Vector2 size) try { - _clipboardText = ImGui.GetClipboardText(); + _clipboardText = ImUtf8.GetClipboardText(); ImGui.OpenPopup("##NewTemplate"); } catch diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplatePanel.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplatePanel.cs index 76b80bf..7e1d8b1 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplatePanel.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/TemplatePanel.cs @@ -3,10 +3,10 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; using System; using System.Linq; using System.Numerics; -using System.Windows.Forms; using CustomizePlus.Core.Data; using CustomizePlus.Templates; using CustomizePlus.Configuration.Data; @@ -230,7 +230,7 @@ private void ExportToClipboard() { try { - Clipboard.SetText(Base64Helper.ExportTemplateToBase64(_selector.Selected!)); + ImUtf8.SetClipboardText(Base64Helper.ExportTemplateToBase64(_selector.Selected!)); _popupSystem.ShowPopup(PopupSystem.Messages.ClipboardDataNotLongTerm); } catch (Exception ex) From 64bbbdd13c39aa7944a36f894494eca3f3fb1fb1 Mon Sep 17 00:00:00 2001 From: Mihai Badea Date: Sun, 22 Feb 2026 17:13:16 +0000 Subject: [PATCH 3/5] refactor to utilize OtterGUI ImgGui clipboard function wrapper class instead of calling System.Windows.Forms / ImGui clipboard functions directly, change tooltip on button to be more clear that it's about exporting a template --- .../UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs index da1e6e2..2f8c410 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Profiles/ProfilePanel.cs @@ -5,6 +5,7 @@ using OtterGui.Raii; using OtterGui.Extensions; using OtterGui.Log; +using OtterGui.Text; using System; using System.Linq; using System.Numerics; @@ -23,7 +24,6 @@ using CustomizePlus.Core.Extensions; using Dalamud.Interface.Components; using OtterGui.Extensions; -using System.Windows.Forms; namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Profiles; @@ -110,7 +110,7 @@ private HeaderDrawer.Button ExportToClipboardButton() => _selector.Selected == null ? HeaderDrawer.Button.Invisible :new HeaderDrawer.Button { - Description = "Copy the current profile to your clipboard.", + Description = "Copy the current profile combined into one template to your clipboard.", Icon = FontAwesomeIcon.Copy, OnClick = ExportToClipboard, Visible = _selector.Selected != null, @@ -272,8 +272,7 @@ private void ExportToClipboard() { try { - var data = Base64Helper.ExportProfileToBase64(_selector.Selected!); - Clipboard.SetText(data); + ImUtf8.SetClipboardText(Base64Helper.ExportProfileToBase64(_selector.Selected!)); _popupSystem.ShowPopup(PopupSystem.Messages.ClipboardDataNotLongTerm); } catch (Exception ex) From 834e173707c6b986b7e422f455647fb03e04b3e7 Mon Sep 17 00:00:00 2001 From: Mihai Badea Date: Sun, 22 Feb 2026 17:32:14 +0000 Subject: [PATCH 4/5] refactored to use OtterGuI wrapper for clipboard actions instead of System.Windows.Forms clipboard functions. fix popup window size on "You have unsaved changes in the current template", as the keep editing button was being cut off --- .../Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs index 6be877e..3784730 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs @@ -14,11 +14,11 @@ using OtterGui; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Windows.Forms; using static CustomizePlus.Core.Data.BoneData; using static FFXIVClientStructs.FFXIV.Client.UI.Misc.MinionListModule.Delegates; @@ -477,7 +477,7 @@ public void Draw() if (ImGui.MenuItem("Import Group")) { - var clipboardText = Clipboard.GetText(); + var clipboardText = ImUtf8.GetClipboardText(); if (!string.IsNullOrEmpty(clipboardText)) _pendingImportText = clipboardText; } @@ -504,7 +504,7 @@ public void Draw() { try { - Clipboard.SetText(_pendingClipboardText); + ImUtf8.SetClipboardText(_pendingClipboardText); _logger.Debug("copied to clipboard: " + _pendingClipboardText); } catch (Exception ex) @@ -525,7 +525,7 @@ private void DrawEditorConfirmationPopup() } var viewportSize = ImGui.GetWindowViewport().Size; - ImGui.SetNextWindowSize(new Vector2(viewportSize.X / 4, viewportSize.Y / 12)); + ImGui.SetNextWindowSize(new Vector2(viewportSize.X / 3, viewportSize.Y / 12)); ImGui.SetNextWindowPos(viewportSize / 2, ImGuiCond.Always, new Vector2(0.5f)); using var popup = ImRaii.Popup("SavePopup", ImGuiWindowFlags.Modal); if (!popup) From 2769e5fd5288051eeddd0b38949b759faec76b41 Mon Sep 17 00:00:00 2001 From: Mihai Badea Date: Sun, 22 Feb 2026 18:01:15 +0000 Subject: [PATCH 5/5] re-add imports as im not sure, as it compiles fine but have some weird crashes when using the file browser, and my visual studio is not showing whats used and not that well currently properly --- CustomizePlus/Templates/TemplateEditorManager.cs | 1 + .../UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/CustomizePlus/Templates/TemplateEditorManager.cs b/CustomizePlus/Templates/TemplateEditorManager.cs index 93888d7..fe13775 100644 --- a/CustomizePlus/Templates/TemplateEditorManager.cs +++ b/CustomizePlus/Templates/TemplateEditorManager.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Windows.Forms; namespace CustomizePlus.Templates; diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs index 3784730..2d65176 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Templates/BoneEditorPanel.cs @@ -19,6 +19,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Windows.Forms; using static CustomizePlus.Core.Data.BoneData; using static FFXIVClientStructs.FFXIV.Client.UI.Misc.MinionListModule.Delegates;