Files
HSUI/Config/Profiles/ProfilesManager.cs

1025 lines
32 KiB
C#

using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Logging;
using HSUI.Config.Attributes;
using HSUI.Config.Tree;
using HSUI.Helpers;
using HSUI.Interface;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
namespace HSUI.Config.Profiles
{
public class ProfilesManager
{
#region Singleton
public readonly SectionNode ProfilesNode;
private ProfilesManager()
{
// fake nodes
ProfilesNode = new SectionNode();
ProfilesNode.Name = "Profiles";
NestedSubSectionNode subSectionNode = new NestedSubSectionNode();
subSectionNode.Name = "General";
subSectionNode.Depth = 0;
ProfilesConfigPageNode configPageNode = new ProfilesConfigPageNode();
subSectionNode.Add(configPageNode);
ProfilesNode.Add(subSectionNode);
ConfigurationManager.Instance.AddExtraSectionNode(ProfilesNode);
// default profile
if (!Profiles.ContainsKey(DefaultProfileName))
{
Profile defaultProfile = new Profile(DefaultProfileName);
Profiles.Add(DefaultProfileName, defaultProfile);
}
}
private bool ResetProfileToDefault(string profileName, bool forced = false)
{
string endPath = Path.Combine(ProfilesPath, profileName + ".HSUI");
if (forced)
{
try
{
File.Delete(endPath);
} catch { }
}
if (forced || !File.Exists(endPath))
{
try
{
Directory.CreateDirectory(ProfilesPath);
File.Copy(MediaDefaultProfilePath, endPath);
return true;
}
catch (Exception e)
{
Plugin.Logger.Error("Error copying default profile!: " + e.Message);
}
}
return false;
}
public static void Initialize()
{
bool attemptRepair = false;
try
{
string jsonString = File.ReadAllText(JsonPath);
ProfilesManager? instance = JsonConvert.DeserializeObject<ProfilesManager>(jsonString);
if (instance != null)
{
Instance = instance;
bool needsSave = false;
foreach (Profile profile in Instance.Profiles.Values)
{
needsSave |= profile.AutoSwitchData.ValidateRolesData();
}
if (needsSave)
{
Instance.Save();
}
}
}
catch
{
Instance = new ProfilesManager();
attemptRepair = true;
}
if (Instance == null)
{
Plugin.Logger.Error("Error initializing HSUI's profiles!!!");
return;
}
// attempt to reconstruct profile from files if the Profiles directory is missing
if (attemptRepair &&
!ConfigurationManager.Instance.IsFreshInstall() &&
!Directory.Exists(ProfilesPath))
{
Instance.CurrentProfileName = "Restored Profile";
Profile defaultProfile = new Profile(Instance.CurrentProfileName);
Instance.Profiles.Add(Instance.CurrentProfileName, defaultProfile);
}
// always make sure the default profile file is present
if (!File.Exists(DefaultProfilePath))
{
if (Instance.ResetProfileToDefault(DefaultProfileName))
{
if (Instance.CurrentProfileName == DefaultProfileName)
{
Instance.ReloadCurrentProfile();
}
}
}
Instance.UpdateSelectedIndex();
Instance.InitializeDefaultImportData();
}
private void InitializeDefaultImportData()
{
string importString = File.ReadAllText(MediaDefaultProfilePath);
string[] importStrings = importString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
if (importStrings.Length == 0)
{
return;
}
foreach (var str in importStrings)
{
try
{
ImportData importData = new ImportData(str);
_defaultImportData.Add(importData);
}
catch { }
}
}
public static ProfilesManager Instance { get; private set; } = null!;
~ProfilesManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
Instance = null!;
}
#endregion
private string _currentProfileName = "Default";
public string CurrentProfileName
{
get => _currentProfileName;
set
{
if (_currentProfileName == value)
{
return;
}
_currentProfileName = value;
if (_currentProfileName == null || _currentProfileName.Length == 0)
{
_currentProfileName = DefaultProfileName;
}
UpdateSelectedIndex();
}
}
[JsonIgnore] private static string ProfilesPath => Path.Combine(ConfigurationManager.Instance.ConfigDirectory, "Profiles");
[JsonIgnore] private static string JsonPath => Path.Combine(ProfilesPath, "Profiles.json");
[JsonIgnore] private static string MediaDefaultProfilePath => Path.Combine(Plugin.AssemblyLocation, "Media", "Profiles", DefaultProfileName + ".HSUI");
[JsonIgnore] private static string DefaultProfileName = "Default";
[JsonIgnore] private static string DefaultProfilePath = Path.Combine(ProfilesPath, DefaultProfileName + ".HSUI");
[JsonIgnore] private List<ImportData> _defaultImportData = new List<ImportData>();
[JsonIgnore] private string _newProfileName = "";
[JsonIgnore] private int _copyFromIndex = 0;
[JsonIgnore] private int _selectedProfileIndex = 0;
[JsonIgnore] private string? _errorMessage = null;
[JsonIgnore] private string? _deletingProfileName = null;
[JsonIgnore] private string? _resetingProfileName = null;
[JsonIgnore] private string? _renamingProfileName = null;
[JsonIgnore] private FileDialogManager _fileDialogManager = new FileDialogManager();
public SortedList<string, Profile> Profiles = new SortedList<string, Profile>();
public ImportData? DefaultImportData(Type type)
{
return _defaultImportData.FirstOrDefault(o => o.ConfigType == type);
}
public Profile CurrentProfile()
{
if (_currentProfileName == null || _currentProfileName.Length == 0)
{
_currentProfileName = DefaultProfileName;
}
return Profiles[_currentProfileName];
}
public void SaveCurrentProfile()
{
if (ConfigurationManager.Instance == null)
{
return;
}
try
{
Save();
SaveCurrentProfile(ConfigurationManager.Instance.ExportCurrentConfigs());
}
catch (Exception e)
{
Plugin.Logger.Error("Error saving profile: " + e.Message);
}
}
public void SaveCurrentProfile(string? exportString)
{
if (exportString == null)
{
return;
}
try
{
Directory.CreateDirectory(ProfilesPath);
File.WriteAllText(CurrentProfilePath(), exportString);
}
catch (Exception e)
{
Plugin.Logger.Error("Error saving profile: " + e.Message);
}
}
public bool LoadCurrentProfile(string oldProfile)
{
try
{
string importString = File.ReadAllText(CurrentProfilePath());
return ConfigurationManager.Instance.ImportProfile(oldProfile, _currentProfileName, importString);
}
catch (Exception e)
{
Plugin.Logger.Error("Error loading profile: " + e.Message);
}
return false;
}
private bool ReloadCurrentProfile()
{
try
{
string importString = File.ReadAllText(CurrentProfilePath());
if (ConfigurationManager.Instance.ImportProfile(_currentProfileName, _currentProfileName, importString, true))
{
ConfigurationManager.Instance.SaveConfigurations(true);
return true;
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error re-loading profile: " + e.Message);
}
return false;
}
public void UpdateCurrentProfile()
{
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (player == null)
{
return;
}
uint jobId = player.ClassJob.RowId;
Profile currentProfile = CurrentProfile();
JobRoles role = JobsHelper.RoleForJob(jobId);
int index = JobsHelper.JobsByRole[role].IndexOf(jobId);
if (index < 0)
{
return;
}
// current profile is enabled for this job, do nothing
if (currentProfile.AutoSwitchEnabled && currentProfile.AutoSwitchData.IsEnabled(role, index))
{
return;
}
// find a profile that is enabled for this job
foreach (Profile profile in Profiles.Values)
{
if (!profile.AutoSwitchEnabled || profile == currentProfile)
{
continue;
}
// found a valid profile, switch to it
if (profile.AutoSwitchData.IsEnabled(role, index))
{
SwitchToProfile(profile.Name);
return;
}
}
}
public void CheckUpdateSwitchCurrentProfile(string specifiedProfile)
{
// found a valid profile, switch to it
if (Profiles.ContainsKey(specifiedProfile))
{
SwitchToProfile(specifiedProfile);
}
}
private unsafe string? SwitchToProfile(string profile, bool save = true)
{
// save if needed before switching
if (save)
{
ConfigurationManager.Instance.SaveConfigurations();
}
string oldProfile = _currentProfileName;
_currentProfileName = profile;
Profile currentProfile = CurrentProfile();
if (currentProfile.AttachHudEnabled && currentProfile.HudLayout != 0)
{
AddonConfig.Instance()->ChangeHudLayout((uint)currentProfile.HudLayout - 1);
}
if (!LoadCurrentProfile(oldProfile))
{
_currentProfileName = oldProfile;
return "Couldn't load profile \"" + profile + "\"!";
}
UpdateSelectedIndex();
try
{
Save();
FontsManager.Instance.BuildFonts();
}
catch (Exception e)
{
Plugin.Logger.Error("Error saving profile: " + e.Message);
return "Couldn't load profile \"" + profile + "\"!";
}
return null;
}
private void UpdateSelectedIndex()
{
_selectedProfileIndex = Math.Max(0, Profiles.IndexOfKey(_currentProfileName));
}
private string CurrentProfilePath()
{
return Path.Combine(ProfilesPath, _currentProfileName + ".HSUI");
}
private string? CloneProfile(string profileName, string newProfileName)
{
string srcPath = Path.Combine(ProfilesPath, profileName + ".HSUI");
string dstPath = Path.Combine(ProfilesPath, newProfileName + ".HSUI");
return CloneProfile(profileName, srcPath, newProfileName, dstPath);
}
private string? CloneProfile(string profileName, string srcPath, string newProfileName, string dstPath)
{
if (newProfileName.Length == 0)
{
return null;
}
if (Profiles.Keys.Contains(newProfileName))
{
return "A profile with the name \"" + newProfileName + "\" already exists!";
}
try
{
if (!File.Exists(srcPath))
{
return "Couldn't find profile \"" + profileName + "\"!";
}
if (File.Exists(dstPath))
{
return "A profile with the name \"" + newProfileName + "\" already exists!";
}
File.Copy(srcPath, dstPath);
Profile newProfile = new Profile(newProfileName);
Profiles.Add(newProfileName, newProfile);
Save();
}
catch (Exception e)
{
Plugin.Logger.Error("Error cloning profile: " + e.Message);
return "Error trying to clone profile \"" + profileName + "\"!";
}
return null;
}
private string? RenameCurrentProfile(string newProfileName)
{
if (_currentProfileName == newProfileName || newProfileName.Length == 0)
{
return null;
}
if (Profiles.ContainsKey(newProfileName))
{
return "A profile with the name \"" + newProfileName + "\" already exists!";
}
string srcPath = Path.Combine(ProfilesPath, _currentProfileName + ".HSUI");
string dstPath = Path.Combine(ProfilesPath, newProfileName + ".HSUI");
try
{
if (File.Exists(dstPath))
{
return "A profile with the name \"" + newProfileName + "\" already exists!";
}
File.Move(srcPath, dstPath);
Profile profile = Profiles[_currentProfileName];
profile.Name = newProfileName;
Profiles.Remove(_currentProfileName);
Profiles.Add(newProfileName, profile);
_currentProfileName = newProfileName;
Save();
}
catch (Exception e)
{
Plugin.Logger.Error("Error renaming profile: " + e.Message);
return "Error trying to rename profile \"" + _currentProfileName + "\"!";
}
return null;
}
private string? Import(string newProfileName, string importString)
{
if (newProfileName.Length == 0)
{
return null;
}
if (Profiles.Keys.Contains(newProfileName))
{
return "A profile with the name \"" + newProfileName + "\" already exists!";
}
string dstPath = Path.Combine(ProfilesPath, newProfileName + ".HSUI");
try
{
if (File.Exists(dstPath))
{
return "A profile with the name \"" + newProfileName + "\" already exists!";
}
File.WriteAllText(dstPath, importString);
Profile newProfile = new Profile(newProfileName);
Profiles.Add(newProfileName, newProfile);
string? errorMessage = SwitchToProfile(newProfileName, false);
if (errorMessage != null)
{
Profiles.Remove(newProfileName);
File.Delete(dstPath);
Save();
return errorMessage;
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error importing profile: " + e.Message);
return "Error trying to import profile \"" + newProfileName + "\"!";
}
return null;
}
private string? ImportFromClipboard(string newProfileName)
{
string importString = ImGui.GetClipboardText();
if (importString.Length == 0)
{
return "Invalid import string!";
}
return Import(newProfileName, importString);
}
private void ImportFromFile(string newProfileName)
{
if (newProfileName.Length == 0)
{
return;
}
Action<bool, string> callback = (finished, path) =>
{
try
{
if (finished && path.Length > 0)
{
string importString = File.ReadAllText(path);
_errorMessage = Import(newProfileName, importString);
if (_errorMessage == null)
{
_newProfileName = "";
}
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error reading import file: " + e.Message);
_errorMessage = "Error reading the file!";
}
};
_fileDialogManager.OpenFileDialog("Select a HSUI Profile to import", "HSUI Profile{.HSUI}", callback);
}
private void ExportToFile(string newProfileName)
{
if (newProfileName.Length == 0)
{
return;
}
Action<bool, string> callback = (finished, path) =>
{
try
{
string src = CurrentProfilePath();
if (finished && path.Length > 0 && src != path)
{
File.Copy(src, path, true);
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error copying file: " + e.Message);
_errorMessage = "Error exporting the file!";
}
};
_fileDialogManager.SaveFileDialog("Save Profile", "HSUI Profile{.HSUI}", newProfileName + ".HSUI", ".HSUI", callback);
}
private string? DeleteProfile(string profileName)
{
if (!Profiles.ContainsKey(profileName))
{
return "Couldn't find profile \"" + profileName + "\"!";
}
string path = Path.Combine(ProfilesPath, profileName + ".HSUI");
try
{
if (!File.Exists(path))
{
return "Couldn't find profile \"" + profileName + "\"!";
}
File.Delete(path);
Profiles.Remove(profileName);
Save();
ConfigurationManager.Instance.OnProfileDeleted(profileName);
if (_currentProfileName == profileName)
{
return SwitchToProfile(DefaultProfileName, false);
}
}
catch (Exception e)
{
Plugin.Logger.Error("Error deleting profile: " + e.Message);
return "Error trying to delete profile \"" + profileName + "\"!";
}
return null;
}
private void Save()
{
string jsonString = JsonConvert.SerializeObject(
this,
Formatting.Indented,
new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Objects
}
);
Directory.CreateDirectory(ProfilesPath);
File.WriteAllText(JsonPath, jsonString);
}
public bool Draw(ref bool changed)
{
string[] profiles = Profiles.Keys.ToArray();
if (ImGui.BeginChild("Profiles", new Vector2(800, 600), false))
{
if (Profiles.Count == 0)
{
ImGuiHelper.Tab();
ImGui.Text("Profiles not found in \"%appdata%/Roaming/XIVLauncher/pluginConfigs/HSUI/Profiles/\"");
return false;
}
ImGui.PushItemWidth(408);
ImGuiHelper.NewLineAndTab();
if (ImGui.Combo("Active Profile", ref _selectedProfileIndex, profiles, 10))
{
string newProfileName = profiles[_selectedProfileIndex];
if (_currentProfileName != newProfileName)
{
_errorMessage = SwitchToProfile(newProfileName);
}
}
// reset
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button("\uf2f9", new Vector2(0, 0)))
{
_resetingProfileName = _currentProfileName;
}
ImGui.PopFont();
ImGuiHelper.SetTooltip("Reset");
if (_currentProfileName != DefaultProfileName)
{
// rename
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Pen.ToIconString()))
{
_renamingProfileName = _currentProfileName;
}
ImGui.PopFont();
ImGuiHelper.SetTooltip("Rename");
// delete
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (_currentProfileName != DefaultProfileName && ImGui.Button(FontAwesomeIcon.Trash.ToIconString()))
{
_deletingProfileName = _currentProfileName;
}
ImGui.PopFont();
ImGuiHelper.SetTooltip("Delete");
}
// export to string
ImGuiHelper.Tab();
ImGui.SameLine();
if (ImGui.Button("Export to Clipboard", new Vector2(200, 0)))
{
string? exportString = ConfigurationManager.Instance.ExportCurrentConfigs();
if (exportString != null)
{
ImGui.SetClipboardText(exportString);
ImGui.OpenPopup("export_succes_popup");
}
}
// export success popup
if (ImGui.BeginPopup("export_succes_popup"))
{
ImGui.Text("Profile export string copied to clipboard!");
ImGui.EndPopup();
}
ImGui.SameLine();
if (ImGui.Button("Export to File", new Vector2(200, 0)))
{
ExportToFile(_currentProfileName);
}
ImGuiHelper.NewLineAndTab();
DrawAttachHudLayout(ref changed);
ImGuiHelper.NewLineAndTab();
DrawAutoSwitchSettings(ref changed);
ImGuiHelper.DrawSeparator(1, 1);
ImGuiHelper.Tab();
ImGui.Text("Create a new profile:");
ImGuiHelper.Tab();
ImGui.PushItemWidth(408);
ImGui.InputText("Profile Name", ref _newProfileName, 200);
ImGuiHelper.Tab();
ImGui.PushItemWidth(200);
ImGui.Combo("", ref _copyFromIndex, profiles, 10);
ImGui.SameLine();
if (ImGui.Button("Copy", new Vector2(200, 0)))
{
_newProfileName = _newProfileName.Trim();
if (_newProfileName.Length == 0)
{
ImGui.OpenPopup("import_error_popup");
}
else
{
_errorMessage = CloneProfile(profiles[_copyFromIndex], _newProfileName);
if (_errorMessage == null)
{
_errorMessage = SwitchToProfile(_newProfileName);
_newProfileName = "";
}
}
}
ImGuiHelper.NewLineAndTab();
if (ImGui.Button("Import From Clipboard", new Vector2(200, 0)))
{
_newProfileName = _newProfileName.Trim();
if (_newProfileName.Length == 0)
{
ImGui.OpenPopup("import_error_popup");
}
else
{
_errorMessage = ImportFromClipboard(_newProfileName);
if (_errorMessage == null)
{
_newProfileName = "";
}
}
}
ImGui.SameLine();
if (ImGui.Button("Import From File", new Vector2(200, 0)))
{
_newProfileName = _newProfileName.Trim();
if (_newProfileName.Length == 0)
{
ImGui.OpenPopup("import_error_popup");
}
else
{
ImportFromFile(_newProfileName);
}
}
// no name popup
if (ImGui.BeginPopup("import_error_popup"))
{
ImGui.Text("Please type a name for the new profile!");
ImGui.EndPopup();
}
}
ImGui.EndChild();
// error message
if (_errorMessage != null)
{
if (ImGuiHelper.DrawErrorModal(_errorMessage))
{
_errorMessage = null;
}
}
// delete confirmation
if (_deletingProfileName != null)
{
string[] lines = new string[] { "Are you sure you want to delete the profile:", " - " + _deletingProfileName };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Delete?", lines);
if (didConfirm)
{
_errorMessage = DeleteProfile(_deletingProfileName);
changed = true;
}
if (didConfirm || didClose)
{
_deletingProfileName = null;
}
}
// reset confirmation
if (_resetingProfileName != null)
{
string[] lines = new string[] { "Are you sure you want to reset the profile:", " - " + _resetingProfileName };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Reset?", lines);
if (didConfirm)
{
ResetProfileToDefault(_resetingProfileName, true);
ReloadCurrentProfile();
changed = true;
}
if (didConfirm || didClose)
{
_resetingProfileName = null;
}
}
// rename modal
if (_renamingProfileName != null)
{
var (didConfirm, didClose) = ImGuiHelper.DrawInputModal("Rename", "Type a new name for the profile:", ref _renamingProfileName);
if (didConfirm)
{
_errorMessage = RenameCurrentProfile(_renamingProfileName);
changed = true;
}
if (didConfirm || didClose)
{
_renamingProfileName = null;
}
}
_fileDialogManager.Draw();
return false;
}
private void DrawAutoSwitchSettings(ref bool changed)
{
Profile profile = CurrentProfile();
changed |= ImGui.Checkbox("Auto-Switch For Specific Jobs", ref profile.AutoSwitchEnabled);
if (!profile.AutoSwitchEnabled)
{
return;
}
AutoSwitchData data = profile.AutoSwitchData;
Vector2 cursorPos = ImGui.GetCursorPos() + new Vector2(14, 14);
Vector2 originalPos = cursorPos;
float maxY = 0;
JobRoles[] roles = (JobRoles[])Enum.GetValues(typeof(JobRoles));
foreach (JobRoles role in roles)
{
if (role == JobRoles.Unknown) { continue; }
if (!data.Map.ContainsKey(role)) { continue; }
bool roleValue = data.GetRoleEnabled(role);
string roleName = JobsHelper.RoleNames[role];
ImGui.SetCursorPos(cursorPos);
if (ImGui.Checkbox(roleName, ref roleValue))
{
data.SetRoleEnabled(role, roleValue);
changed = true;
}
cursorPos.Y += 40;
int jobCount = data.Map[role].Count;
for (int i = 0; i < jobCount; i++)
{
maxY = Math.Max(cursorPos.Y, maxY);
uint jobId = JobsHelper.JobsByRole[role][i];
bool jobValue = data.Map[role][i];
string jobName = JobsHelper.JobNames[jobId];
ImGui.SetCursorPos(cursorPos);
if (ImGui.Checkbox(jobName, ref jobValue))
{
data.Map[role][i] = jobValue;
changed = true;
}
cursorPos.Y += 30;
}
cursorPos.X += 100;
cursorPos.Y = originalPos.Y;
}
ImGui.SetCursorPos(new Vector2(originalPos.X, maxY + 30));
}
private void DrawAttachHudLayout(ref bool changed)
{
Profile profile = CurrentProfile();
changed |= ImGui.Checkbox("Attach HUD Layout to this profile", ref profile.AttachHudEnabled);
if (!profile.AttachHudEnabled)
{
profile.HudLayout = 0;
return;
}
int hudLayout = profile.HudLayout;
ImGui.Text("\u2514");
for (int i = 1; i <= 4; i++)
{
ImGui.SameLine();
bool hudLayoutEnabled = hudLayout == i;
if (ImGui.Checkbox("Hud Layout " + i, ref hudLayoutEnabled))
{
profile.HudLayout = i;
changed = true;
}
}
}
}
// fake config object
[Disableable(false)]
[Exportable(false)]
[Shareable(false)]
[Resettable(false)]
public class ProfilesConfig : PluginConfigObject
{
public new static ProfilesConfig DefaultConfig() { return new ProfilesConfig(); }
}
// fake config page node
public class ProfilesConfigPageNode : ConfigPageNode
{
public ProfilesConfigPageNode()
{
ConfigObject = new ProfilesConfig();
}
public override bool Draw(ref bool changed)
{
return ProfilesManager.Instance?.Draw(ref changed) ?? false;
}
}
}