diff --git a/EngageTimer.sln.DotSettings b/EngageTimer.sln.DotSettings index 0df9d57..94fd139 100644 --- a/EngageTimer.sln.DotSettings +++ b/EngageTimer.sln.DotSettings @@ -1,9 +1,6 @@ - + This file is part of EngageTimer -Copyright (C) $CURRENT_YEAR$ Xorus <xorus@posteo.net> +Copyright (C) ${CurrentDate.Year} Xorus <xorus@posteo.net> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -15,4 +12,5 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License -along with this program. If not, see <https://www.gnu.org/licenses/>. \ No newline at end of file +along with this program. If not, see <https://www.gnu.org/licenses/>. + True \ No newline at end of file diff --git a/Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs b/Plugin/Configuration/CombatAlarmsConfiguration.cs similarity index 97% rename from Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs rename to Plugin/Configuration/CombatAlarmsConfiguration.cs index ea94f13..0570feb 100644 --- a/Plugin/Configuration/Legacy/CombatAlarmsConfiguration.cs +++ b/Plugin/Configuration/CombatAlarmsConfiguration.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; using System.Numerics; -namespace EngageTimer.Configuration.Legacy; +namespace EngageTimer.Configuration; public class CombatAlarmsConfiguration { diff --git a/Plugin/Plugin.cs b/Plugin/Plugin.cs index 1d2f2cc..622110b 100644 --- a/Plugin/Plugin.cs +++ b/Plugin/Plugin.cs @@ -50,7 +50,7 @@ public sealed class Plugin : IDalamudPlugin private static FrameworkThings FrameworkThings { get; set; } = null!; private static MainCommand MainCommand { get; set; } = null!; private static SettingsCommand SettingsCommand { get; set; } = null!; - private static CombatAlarm CombatAlarm { get; set; } = null!; + public static CombatAlarm CombatAlarm { get; set; } = null!; public static SfxPlay SfxPlay { get; set; } = null!; public Plugin(DalamudPluginInterface pluginInterface) diff --git a/Plugin/Properties/Resources.Designer.cs b/Plugin/Properties/Resources.Designer.cs index 8480d16..81ce3a5 100644 --- a/Plugin/Properties/Resources.Designer.cs +++ b/Plugin/Properties/Resources.Designer.cs @@ -77,6 +77,15 @@ internal static string AlarmEdit_Active_Tooltip { } } + /// + /// Looks up a localized string similar to Create new. + /// + internal static string AlarmEdit_Add { + get { + return ResourceManager.GetString("AlarmEdit_Add", resourceCulture); + } + } + /// /// Looks up a localized string similar to Blink. /// @@ -95,6 +104,24 @@ internal static string AlarmEdit_Blink_Tooltip { } } + /// + /// Looks up a localized string similar to Clear all. + /// + internal static string AlarmEdit_Clear { + get { + return ResourceManager.GetString("AlarmEdit_Clear", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Do you really want to remove all the alarms?. + /// + internal static string AlarmEdit_Clear_Confirm { + get { + return ResourceManager.GetString("AlarmEdit_Clear_Confirm", resourceCulture); + } + } + /// /// Looks up a localized string similar to Color. /// @@ -131,6 +158,51 @@ internal static string AlarmEdit_Duration_Tooltip { } } + /// + /// Looks up a localized string similar to Export. + /// + internal static string AlarmEdit_Export { + get { + return ResourceManager.GetString("AlarmEdit_Export", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select file to export. + /// + internal static string AlarmEdit_Export_File { + get { + return ResourceManager.GetString("AlarmEdit_Export_File", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exports the active alarms to a file. + /// + internal static string AlarmEdit_Export_Tooltip { + get { + return ResourceManager.GetString("AlarmEdit_Export_Tooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Import. + /// + internal static string AlarmEdit_Import { + get { + return ResourceManager.GetString("AlarmEdit_Import", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select file to import. + /// + internal static string AlarmEdit_Import_File { + get { + return ResourceManager.GetString("AlarmEdit_Import_File", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sound. /// @@ -194,6 +266,15 @@ internal static string AlarmEdit_Text_Clear { } } + /// + /// Looks up a localized string similar to Test. + /// + internal static string AlarmEdit_Text_Test { + get { + return ResourceManager.GetString("AlarmEdit_Text_Test", resourceCulture); + } + } + /// /// Looks up a localized string similar to Text. /// @@ -248,6 +329,51 @@ internal static string AlarmEdit_Type_GameToast { } } + /// + /// Looks up a localized string similar to Could not save the file in this directory (access denied). Please try again with another directory.. + /// + internal static string CombatAlarm_AccessDenied { + get { + return ResourceManager.GetString("CombatAlarm_AccessDenied", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An empty or invalid alarm list was imported. + /// + internal static string CombatAlarm_ImportedEmpty { + get { + return ResourceManager.GetString("CombatAlarm_ImportedEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The alarms file format is incompatible or incorrect. + /// + internal static string CombatAlarm_IncorrectFormat { + get { + return ResourceManager.GetString("CombatAlarm_IncorrectFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occured while trying to read the alarms to {0}: {1}.. + /// + internal static string CombatAlarm_ReadGeneric { + get { + return ResourceManager.GetString("CombatAlarm_ReadGeneric", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occured while trying to save the alarms to {0}: {1}.. + /// + internal static string CombatAlarm_SaveGeneric { + get { + return ResourceManager.GetString("CombatAlarm_SaveGeneric", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unrecognized argument for {0}: {1}. /// @@ -347,6 +473,33 @@ internal static string MainCommand_Status_On { } } + /// + /// Looks up a localized string similar to Cancel. + /// + internal static string Modal_Cancel { + get { + return ResourceManager.GetString("Modal_Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confirm. + /// + internal static string Modal_Confirm { + get { + return ResourceManager.GetString("Modal_Confirm", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OK. + /// + internal static string Modal_Ok { + get { + return ResourceManager.GetString("Modal_Ok", resourceCulture); + } + } + /// /// Looks up a localized string similar to This page allows you to set alarms based on elapsed combat time.. /// diff --git a/Plugin/Properties/Resources.resx b/Plugin/Properties/Resources.resx index b3c723c..da6cde3 100644 --- a/Plugin/Properties/Resources.resx +++ b/Plugin/Properties/Resources.resx @@ -566,4 +566,55 @@ help text for the /eg settings command A common use is to notify you of the potion window. + + Create new + + + Clear all + + + Do you really want to remove all the alarms? + + + Export + + + Import + + + Test + + + Exports the active alarms to a file + + + Select file to import + + + Select file to export + + + OK + + + Cancel + + + Confirm + + + Could not save the file in this directory (access denied). Please try again with another directory. + + + An error occured while trying to save the alarms to {0}: {1}. + + + An error occured while trying to read the alarms to {0}: {1}. + + + The alarms file format is incompatible or incorrect + + + An empty or invalid alarm list was imported + \ No newline at end of file diff --git a/Plugin/Status/CombatAlarm.cs b/Plugin/Status/CombatAlarm.cs index dacfea8..c2d6930 100644 --- a/Plugin/Status/CombatAlarm.cs +++ b/Plugin/Status/CombatAlarm.cs @@ -15,11 +15,14 @@ using System; using System.Collections.Generic; +using System.IO; using Dalamud.Game.Text; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; -using EngageTimer.Configuration.Legacy; +using EngageTimer.Configuration; using EngageTimer.Game; +using EngageTimer.Ui; +using Newtonsoft.Json; namespace EngageTimer.Status; @@ -50,6 +53,57 @@ public CombatAlarm() Plugin.State.InCombatChanged += InCombatChanged; } + public static string? Import(string fileName) + { + try + { + var text = File.ReadAllText(fileName); + var data = JsonConvert.DeserializeObject(text, + new JsonSerializerSettings + { + // using "TypeNameHandling.Objects" causes a "resolving to a collectible assembly is not supported" + TypeNameHandling = TypeNameHandling.None + }); + if (data == null || data.Alarms.Count == 0) return Translator.Tr("CombatAlarm_ImportedEmpty"); + Plugin.Config.CombatAlarms.Alarms.AddRange(data.Alarms); + } + catch (JsonSerializationException e) + { + Plugin.Logger.Error(e, $"Could not parse file {fileName}"); + return Translator.Tr("CombatAlarm_IncorrectFormat"); + } + catch (Exception e) + { + Plugin.Logger.Error(e, $"Could not read file {fileName}"); + return Translator.Tr("CombatAlarm_ReadGeneric", fileName, e.Message); + } + + return null; + } + + public static string? Export(string fileName) + { + try + { + File.WriteAllText(fileName, + JsonConvert.SerializeObject(Plugin.Config.CombatAlarms, Formatting.Indented, + new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.None + })); + } + catch (UnauthorizedAccessException) + { + return Translator.Tr("CombatAlarm_AccessDenied"); + } + catch (Exception e) + { + Plugin.Logger.Error(e, $"Could not save file {fileName}"); + return Translator.Tr("CombatAlarm_SaveGeneric", fileName, e.Message); + } + + return null; + } private void ConfigurationChanged(object? sender, EventArgs e) { @@ -88,7 +142,7 @@ private void FrameworkUpdate(IFramework framework) if (!Plugin.State.InCombat) return; // only run once a second - var time = (int)Math.Floor(Plugin.State.CombatDuration.TotalSeconds); + var time = (int) Math.Floor(Plugin.State.CombatDuration.TotalSeconds); if (_lastCheck == time) return; _lastCheck = time; @@ -107,14 +161,14 @@ public static void AlarmSfx(CombatAlarmsConfiguration.Alarm alarm) { if (alarm.Sfx != null) { - Plugin.SfxPlay.SoundEffect((uint)(SfxPlay.FirstSeSfx + alarm.Sfx)); + Plugin.SfxPlay.SoundEffect((uint) (SfxPlay.FirstSeSfx + alarm.Sfx)); } } public static void AlarmText(CombatAlarmsConfiguration.Alarm alarm) { var trimText = alarm.Text?.Trim(); - if (trimText is not { Length: > 0 }) return; + if (trimText is not {Length: > 0}) return; switch (alarm.TextType) { case CombatAlarmsConfiguration.TextType.DalamudNotification: diff --git a/Plugin/Ui/Components.cs b/Plugin/Ui/Components.cs index 30f119d..454c21f 100644 --- a/Plugin/Ui/Components.cs +++ b/Plugin/Ui/Components.cs @@ -322,4 +322,20 @@ public static void TooltipOnItemHovered(string text) ImGui.PopTextWrapPos(); ImGui.EndTooltip(); } + + public delegate void DrawAction(); + + public static void LeftRight(string id, DrawAction left, DrawAction right) + { + if (!ImGui.BeginTable(id, 3, ImGuiTableFlags.SizingFixedFit)) return; + ImGui.TableSetupColumn("left"); + ImGui.TableSetupColumn("spacer", ImGuiTableColumnFlags.WidthStretch, 100); + ImGui.TableSetupColumn("right"); + ImGui.TableNextColumn(); + left(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + right(); + ImGui.EndTable(); + } } \ No newline at end of file diff --git a/Plugin/Ui/Modal.cs b/Plugin/Ui/Modal.cs new file mode 100644 index 0000000..dc62816 --- /dev/null +++ b/Plugin/Ui/Modal.cs @@ -0,0 +1,73 @@ +// This file is part of EngageTimer +// Copyright (C) 2024 Xorus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using System.Numerics; +using ImGuiNET; + +namespace EngageTimer.Ui; + +public class Modal +{ + private bool _visible = false; + private const string Title = "EngageTimer####egmodal"; + private string _message = ""; + private Action? _validate = null; + + public void Draw() + { + if (_visible) + { + var center = ImGui.GetMainViewport().GetCenter(); + ImGui.SetNextWindowPos(center, ImGuiCond.Always, new Vector2(0.5f, 0.5f)); + ImGui.SetNextWindowSize(new Vector2(300, 0)); + } + + if (!ImGui.BeginPopupModal(Title, ref _visible)) return; + ImGui.TextWrapped(_message); + + if (_validate != null) + { + if (ImGui.Button(Translator.Tr("Modal_Cancel"), new Vector2(120, 0))) + ImGui.CloseCurrentPopup(); + ImGui.SameLine(); + if (ImGui.Button(Translator.Tr("Modal_Confirm"), new Vector2(120, 0))) + { + ImGui.CloseCurrentPopup(); + _validate(); + } + } + else if (ImGui.Button(Translator.Tr("Modal_Ok"), new Vector2(120, 0))) + ImGui.CloseCurrentPopup(); + + ImGui.EndPopup(); + } + + public void Show(string message) + { + _message = message; + _visible = true; + _validate = null; + ImGui.OpenPopup(Title); + } + + public void Confirm(string message, Action validate) + { + _message = message; + _validate = validate; + _visible = true; + ImGui.OpenPopup(Title); + } +} \ No newline at end of file diff --git a/Plugin/Ui/SettingsTab/AlarmsTab.cs b/Plugin/Ui/SettingsTab/AlarmsTab.cs index a28c990..a5cde89 100644 --- a/Plugin/Ui/SettingsTab/AlarmsTab.cs +++ b/Plugin/Ui/SettingsTab/AlarmsTab.cs @@ -15,12 +15,10 @@ using System; using System.Collections.Generic; -using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Components; -using Dalamud.Interface.Utility; -using EngageTimer.Configuration.Legacy; -using EngageTimer.Game; +using Dalamud.Interface.ImGuiFileDialog; +using EngageTimer.Configuration; using EngageTimer.Status; using ImGuiNET; @@ -28,6 +26,9 @@ namespace EngageTimer.Ui.SettingsTab; public static class AlarmsTab { + private static readonly FileDialogManager Fdm = new(); + private static readonly Modal Modal = new(); + private static void Header(bool tooltip = false) { ImGui.TableNextColumn(); @@ -36,6 +37,11 @@ private static void Header(bool tooltip = false) if (tooltip) Components.TooltipOnItemHovered(Translator.Tr(str + "_Tooltip")); } + /** + * Can't open a popup from within a table apparently + */ + private static bool _openConfirmClear = false; + private static readonly List EditingTexts = new(); public static void Draw() @@ -53,12 +59,14 @@ public static void Draw() ImGui.TableSetupColumn("AlarmEdit_Blink"); ImGui.TableSetupColumn("AlarmEdit_Duration"); ImGui.TableSetupColumn("AlarmEdit_Sound"); - ImGui.TableSetupColumn("AlarmEdit_Text"); + ImGui.TableSetupColumn("AlarmEdit_Text", ImGuiTableColumnFlags.WidthStretch); ImGui.TableNextRow(ImGuiTableRowFlags.Headers); // trash can - ImGui.TableNextColumn(); - ImGui.Text(""); + { + ImGui.TableNextColumn(); + ImGui.Text(""); + } Header(true); // active Header(true); // start time Header(true); // color @@ -69,23 +77,95 @@ public static void Draw() for (var index = 0; index < Plugin.Config.CombatAlarms.Alarms.Count; index++) { + ImGui.PushID("alarm" + index); AlarmElement(index, Plugin.Config.CombatAlarms.Alarms[index]); + ImGui.PopID(); } ImGui.EndTable(); } - if (ImGuiComponents.IconButton($"{FontAwesomeIcon.Plus.ToIconString()}###add")) + Components.LeftRight("buttons", () => { + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.FileImport, Translator.Tr("AlarmEdit_Import"))) + { + Fdm.OpenFileDialog( + Translator.Tr("AlarmEdit_Import_File"), + ".json", + (ok, path) => + { + if (!ok) return; + foreach (var p in path) + { + var error = CombatAlarm.Import(p); + if (error != null) Modal.Show(error); + } + }, + 0, + null, + true + ); + } + + + ImGui.SameLine(); + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.FileExport, Translator.Tr("AlarmEdit_Export"))) + { + Fdm.SaveFileDialog( + Translator.Tr("AlarmEdit_Export_File"), + ".json", + "EngageTimerAlarms.json", + "json", + (ok, path) => + { + if (ok) + { + var error = CombatAlarm.Export(path); + Plugin.Logger.Info("got error" + error); + if (error != null) Modal.Show(error); + } + + Plugin.Logger.Info($"got {ok} file {path}"); + }, + null, + true + ); + } + + Components.TooltipOnItemHovered("AlarmEdit_Export_Tooltip"); + + ImGui.SameLine(); + // clear all button + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, Translator.Tr("AlarmEdit_Clear"))) + { + if (Plugin.Config.CombatAlarms.Alarms.Count == 0) return; + _openConfirmClear = true; + } + }, () => + { + if (!ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, Translator.Tr("AlarmEdit_Add"))) return; Plugin.Config.CombatAlarms.Alarms.Add(new CombatAlarmsConfiguration.Alarm()); Plugin.Config.Save(); + }); + + if (_openConfirmClear) + { + _openConfirmClear = false; + Modal.Confirm(Translator.Tr("AlarmEdit_Clear_Confirm"), () => + { + Plugin.Config.CombatAlarms.Alarms.Clear(); + Plugin.Config.Save(); + }); } + + Fdm.Draw(); + Modal.Draw(); } private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alarm) { + ImGui.TableNextRow(); { - ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.PushID("alarm_" + index); Components.IconButton(FontAwesomeIcon.Trash, "delete" + index, @@ -166,13 +246,13 @@ private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alar ImGui.TableNextColumn(); if (EditingTexts.Contains(index)) { - var type = (int)alarm.TextType; + var type = (int) alarm.TextType; ImGui.PushItemWidth(150f); if (ImGui.Combo("Type", ref type, Translator.Tr("AlarmEdit_Type_ChatLog") + "\0" + Translator.Tr("AlarmEdit_Type_DalamudNotification") + "\0" + Translator.Tr("AlarmEdit_Type_GameToast") + "\0")) { - alarm.TextType = (CombatAlarmsConfiguration.TextType)type; + alarm.TextType = (CombatAlarmsConfiguration.TextType) type; Plugin.Config.Save(); } @@ -186,7 +266,7 @@ private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alar ImGui.PopItemWidth(); - if (ImGui.Button(Translator.Tr("AlarmEdit_Text_Type"))) + if (ImGui.Button(Translator.Tr("AlarmEdit_Text_Test"))) { CombatAlarm.AlarmText(alarm); } @@ -240,7 +320,6 @@ private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alar } } } - ImGui.PopID(); } } \ No newline at end of file