Change box dump to use a form

Closes #4122
This commit is contained in:
Kurt 2023-12-30 14:36:33 -08:00
parent a24e6e9cef
commit d25fab0e87
7 changed files with 557 additions and 43 deletions

View File

@ -0,0 +1,154 @@
using System;
using System.IO;
namespace PKHeX.Core;
/// <summary>
/// Logic for exporting a <see cref="SaveFile"/> to a folder of <see cref="PKM"/> files.
/// </summary>
public static class BoxExport
{
/// <summary>
/// File namer to use for exporting if none is provided.
/// </summary>
private static IFileNamer<PKM> Default => EntityFileNamer.Namer;
/// <summary>
/// Export a box in the <see cref="SaveFile"/> to the specified folder.
/// </summary>
/// <param name="sav">Save file to export</param>
/// <param name="destPath">Folder to export to</param>
/// <param name="box">Box to export</param>
/// <param name="settings">Settings to use for exporting</param>
public static int Export(SaveFile sav, string destPath, int box, BoxExportSettings settings)
=> Export(sav, destPath, box, Default, settings);
/// <summary>
/// Export a box in the <see cref="SaveFile"/> to the specified folder.
/// </summary>
/// <param name="sav">Save file to export</param>
/// <param name="destPath">Folder to export to</param>
/// <param name="box">Box to export</param>
/// <param name="namer">File namer to use for exporting</param>
/// <param name="settings">Settings to use for exporting</param>
public static int Export(SaveFile sav, string destPath, int box, IFileNamer<PKM> namer, BoxExportSettings settings)
=> ExportBox(sav, destPath, namer, box, settings, sav.BoxSlotCount, sav.SlotCount);
/// <summary>
/// Export all boxes in the <see cref="SaveFile"/> to the specified folder.
/// </summary>
/// <param name="sav">Save file to export</param>
/// <param name="destPath">Folder to export to</param>
/// <param name="settings">Settings to use for exporting</param>
public static int Export(SaveFile sav, string destPath, BoxExportSettings settings)
=> Export(sav, destPath, Default, settings);
/// <summary>
/// Export all boxes in the <see cref="SaveFile"/> to the specified folder.
/// </summary>
/// <param name="sav">Save file to export</param>
/// <param name="destPath">Folder to export to</param>
/// <param name="namer">File namer to use for exporting</param>
/// <param name="settings">Settings to use for exporting</param>
/// <returns>Number of files exported</returns>
public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, BoxExportSettings settings)
{
if (!sav.HasBox)
return 0;
int total = sav.SlotCount;
int boxSlotCount = sav.BoxSlotCount;
var startBox = settings.Scope == BoxExportScope.Current ? sav.CurrentBox : 0;
var endBox = settings.Scope == BoxExportScope.Current ? startBox + 1 : sav.BoxCount;
var ctr = 0;
// Export each box specified.
for (int box = startBox; box < endBox; box++)
{
var boxFolder = destPath;
if (settings.FolderCreation == BoxExportFolderMode.FolderEachBox)
{
var folderName = GetFolderName(sav, box, settings.FolderPrefix);
boxFolder = Path.Combine(destPath, folderName);
Directory.CreateDirectory(boxFolder);
}
ctr += ExportBox(sav, boxFolder, namer, box, settings, boxSlotCount, total);
}
return ctr;
}
private static int ExportBox(SaveFile sav, string destPath, IFileNamer<PKM> namer, int box, BoxExportSettings settings,
int boxSlotCount, int total)
{
int count = GetSlotCountForBox(boxSlotCount, box, total);
int ctr = 0;
// Export each slot in the box.
for (int slot = 0; slot < count; slot++)
{
var pk = sav.GetBoxSlotAtIndex(box, slot);
if (IsUndesirableForExport(pk))
{
if (settings.EmptySlots == BoxExportEmptySlots.Skip)
continue;
}
var fileName = GetFileName(pk, settings.FileIndexPrefix, namer, box, slot, boxSlotCount);
var fn = Path.Combine(destPath, fileName);
File.WriteAllBytes(fn, pk.DecryptedPartyData);
ctr++;
}
return ctr;
}
private static bool IsUndesirableForExport(PKM pk) => pk.Species == 0 || !pk.Valid;
private static int GetSlotCountForBox(int boxSlotCount, int box, int total)
{
// Account for any jagged-boxes with less than the usual number of slots.
int absoluteStart = boxSlotCount * box;
int count = boxSlotCount;
if (absoluteStart + count > total)
count = total - absoluteStart;
return count;
}
private static string GetFolderName(SaveFile sav, int box, BoxExportFolderNaming mode)
{
var boxName = Util.CleanFileName(sav.GetBoxName(box));
return mode switch
{
BoxExportFolderNaming.BoxName => boxName,
BoxExportFolderNaming.Index => $"{box + 1:00}",
BoxExportFolderNaming.IndexBoxName => $"{box + 1:00} {boxName}",
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
}
private static string GetFileName(PKM pk, BoxExportIndexPrefix mode, IFileNamer<PKM> namer, int box, int slot, int boxSlotCount)
{
var slotName = GetInnerName(namer, pk);
var fileName = Util.CleanFileName(slotName);
var prefix = GetPrefix(mode, box, slot, boxSlotCount);
return $"{prefix}{fileName}.{pk.Extension}";
}
private static string GetPrefix(BoxExportIndexPrefix mode, int box, int slot, int boxSlotCount) => mode switch
{
BoxExportIndexPrefix.None => string.Empty,
BoxExportIndexPrefix.InAll => $"{(box * boxSlotCount) + slot:0000} - ",
BoxExportIndexPrefix.InBox => $"{slot:00} - ",
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};
private static string GetInnerName(IFileNamer<PKM> namer, PKM pk)
{
try
{
var slotName = namer.GetName(pk);
return Util.CleanFileName(slotName);
}
catch { return "Name Error"; }
}
}

View File

@ -0,0 +1,167 @@
using System;
using System.ComponentModel;
namespace PKHeX.Core;
/// <summary>
/// Settings for exporting boxes
/// </summary>
[Serializable]
public sealed record BoxExportSettings
{
/// <summary>
/// Default settings for regular box exports.
/// </summary>
public static BoxExportSettings Default => new();
/// <summary>
/// Scope of the export -- all boxes or just the current box
/// </summary>
[Category("Export")]
public BoxExportScope Scope { get; set; }
/// <summary>
/// Folder creation mode -- no folders, or a folder for each box
/// </summary>
[Category("Folder")]
public BoxExportFolderMode FolderCreation { get; set; }
/// <summary>
/// Naming mode for folders
/// </summary>
/// <remarks>
/// only used if <see cref="FolderCreation"/> is set to <see cref="BoxExportFolderMode.FolderEachBox"/>
/// </remarks>
[Category("Folder")]
public BoxExportFolderNaming FolderPrefix { get; set; }
/// <summary>
/// Empty slot mode -- skip empty slots or include them in the export
/// </summary>
[Category("File")]
public BoxExportEmptySlots EmptySlots { get; set; }
/// <summary>
/// File index prefix mode -- no prefix, or a prefix for each file
/// </summary>
[Category("File")]
public BoxExportIndexPrefix FileIndexPrefix { get; set; }
/// <summary>
/// Export notification settings -- whether to notify the user of the export
/// </summary>
[Category("Export")]
public BoxExportNofify Notify { get; set; }
}
/// <summary>
/// Export scope for boxes
/// </summary>
public enum BoxExportScope : byte
{
/// <summary>
/// All boxes will be exported
/// </summary>
All = 0,
/// <summary>
/// Only the current box will be exported
/// </summary>
Current = 1,
}
/// <summary>
/// Export folder creation mode
/// </summary>
public enum BoxExportFolderMode : byte
{
/// <summary>
/// No folders will be created; all files will be in the same directory
/// </summary>
None = 0,
/// <summary>
/// A folder will be created for each box
/// </summary>
/// <remarks>
/// Settings will reference the associated <see cref="BoxExportFolderNaming"/> value
/// </remarks>
FolderEachBox = 1,
}
/// <summary>
/// Export folder naming mode
/// </summary>
/// <remarks>
/// only used if <see cref="BoxExportFolderMode.FolderEachBox"/> is selected
/// </remarks>
public enum BoxExportFolderNaming : byte
{
/// <summary>
/// The folder will be named after the box name
/// </summary>
BoxName = 0,
/// <summary>
/// The folder will be named after the box index
/// </summary>
Index = 1,
/// <summary>
/// The folder will be named after the box index and box name
/// </summary>
IndexBoxName = 2,
}
/// <summary>
/// Export empty slots mode
/// </summary>
public enum BoxExportEmptySlots : byte
{
/// <summary>
/// Empty/Invalid slots will be skipped
/// </summary>
Skip = 0,
/// <summary>
/// Empty/Invalid slots will be included in the export
/// </summary>
Include = 1,
}
/// <summary>
/// Export file index prefix mode
/// </summary>
public enum BoxExportIndexPrefix : byte
{
/// <summary>
/// No prefix will be added to the file name
/// </summary>
None = 0,
/// <summary>
/// The file name will be prefixed with the box index
/// </summary>
InBox = 1,
/// <summary>
/// The file name will be prefixed with the box index and slot index
/// </summary>
InAll = 2,
}
/// <summary>
/// Export notification mode
/// </summary>
public enum BoxExportNofify : byte
{
/// <summary>
/// Notify the user of the export result
/// </summary>
NotifyResult = 0,
/// <summary>
/// Do not notify the user of the export result
/// </summary>
Silent = 1,
}

View File

@ -937,42 +937,6 @@ private static int ImportGroup(IEnumerable<PKM> data, SaveFile sav, int box, PKM
return slotSkipped;
}
public bool DumpBoxes(out string result, string? path = null, bool separate = false)
{
if (path == null && !IsFolderPath(out path))
{
result = path;
return false;
}
Directory.CreateDirectory(path);
var count = SAV.DumpBoxes(path, separate);
if (count < 0)
result = MsgSaveBoxExportInvalid;
else
result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path;
return true;
}
public bool DumpBox(out string result, string? path = null)
{
if (path == null && !IsFolderPath(out path))
{
result = path;
return false;
}
Directory.CreateDirectory(path);
var count = SAV.DumpBox(path, Box.CurrentBox);
if (count < 0)
result = MsgSaveBoxExportInvalid;
else
result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path;
return true;
}
public bool LoadBoxes(out string result, string? path = null)
{
result = string.Empty;

View File

@ -446,21 +446,24 @@ private void MainMenuBoxLoad(object sender, EventArgs e)
/// </summary>
private void MainMenuBoxDump(object sender, EventArgs e)
{
string? path = null;
DialogResult ld = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDatabaseExport);
if (ld == DialogResult.Yes)
path = DatabasePath;
else if (ld != DialogResult.No)
{
BoxExport.Export(C_SAV.SAV, DatabasePath, BoxExportSettings.Default);
return;
}
if (ld != DialogResult.No)
return;
if (C_SAV.DumpBoxes(out string result, path))
WinFormsUtil.Alert(result);
using var dumper = new BoxExporter(C_SAV.SAV, BoxExporter.ExportOverride.All);
dumper.ShowDialog();
}
private void MainMenuBoxDumpSingle(object sender, EventArgs e)
{
if (C_SAV.DumpBox(out string result))
WinFormsUtil.Alert(result);
C_SAV.SAV.CurrentBox = C_SAV.CurrentBox; // double check
using var dumper = new BoxExporter(C_SAV.SAV, BoxExporter.ExportOverride.Current);
dumper.ShowDialog();
}
private void MainMenuBatchEditor(object sender, EventArgs e)

View File

@ -44,6 +44,9 @@ public sealed class PKHeXSettings
public MysteryGiftDatabaseSettings MysteryDb { get; set; } = new();
public BulkAnalysisSettings Bulk { get; set; } = new();
[Browsable(false)]
public SlotExportSettings SlotExport { get; set; } = new();
private static PKHeXSettingsContext GetContext() => new(new()
{
WriteIndented = true,
@ -451,3 +454,13 @@ public sealed class BulkAnalysisSettings : IBulkAnalysisSettings
[LocalizedDescription("Checks the save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.")]
public bool CheckActiveHandler { get; set; } = true;
}
[Serializable]
public sealed class SlotExportSettings
{
[LocalizedDescription("Settings to use for box exports.")]
public BoxExportSettings BoxExport { get; set; } = new();
[LocalizedDescription("Selected File namer to use for box exports for the GUI, if multiple are available.")]
public string DefaultBoxExportNamer { get; set; } = "";
}

View File

@ -0,0 +1,120 @@
namespace PKHeX.WinForms
{
partial class BoxExporter
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
PG_Settings = new System.Windows.Forms.PropertyGrid();
B_Export = new System.Windows.Forms.Button();
flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
label1 = new System.Windows.Forms.Label();
CB_Namer = new System.Windows.Forms.ComboBox();
flowLayoutPanel1.SuspendLayout();
SuspendLayout();
//
// PG_Settings
//
PG_Settings.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
PG_Settings.CommandsVisibleIfAvailable = false;
PG_Settings.HelpVisible = false;
PG_Settings.Location = new System.Drawing.Point(0, 31);
PG_Settings.Name = "PG_Settings";
PG_Settings.PropertySort = System.Windows.Forms.PropertySort.Categorized;
PG_Settings.Size = new System.Drawing.Size(304, 203);
PG_Settings.TabIndex = 0;
PG_Settings.ToolbarVisible = false;
//
// B_Export
//
B_Export.Dock = System.Windows.Forms.DockStyle.Bottom;
B_Export.Location = new System.Drawing.Point(0, 240);
B_Export.Name = "B_Export";
B_Export.Size = new System.Drawing.Size(304, 41);
B_Export.TabIndex = 1;
B_Export.Text = "Export";
B_Export.UseVisualStyleBackColor = true;
B_Export.Click += B_Export_Click;
//
// flowLayoutPanel1
//
flowLayoutPanel1.Controls.Add(label1);
flowLayoutPanel1.Controls.Add(CB_Namer);
flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
flowLayoutPanel1.Location = new System.Drawing.Point(0, 0);
flowLayoutPanel1.Name = "flowLayoutPanel1";
flowLayoutPanel1.Size = new System.Drawing.Size(304, 27);
flowLayoutPanel1.TabIndex = 2;
//
// label1
//
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(8, 8);
label1.Margin = new System.Windows.Forms.Padding(8, 8, 0, 8);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(46, 15);
label1.TabIndex = 0;
label1.Text = "Namer:";
//
// CB_Namer
//
CB_Namer.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
CB_Namer.FormattingEnabled = true;
CB_Namer.Location = new System.Drawing.Point(54, 4);
CB_Namer.Margin = new System.Windows.Forms.Padding(0, 4, 0, 0);
CB_Namer.Name = "CB_Namer";
CB_Namer.Size = new System.Drawing.Size(155, 23);
CB_Namer.TabIndex = 1;
//
// BoxExporter
//
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
BackColor = System.Drawing.Color.White;
ClientSize = new System.Drawing.Size(304, 281);
Controls.Add(flowLayoutPanel1);
Controls.Add(B_Export);
Controls.Add(PG_Settings);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
MaximizeBox = false;
MinimizeBox = false;
Name = "BoxExporter";
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
Text = "Box Export";
FormClosing += BoxExporter_FormClosing;
flowLayoutPanel1.ResumeLayout(false);
flowLayoutPanel1.PerformLayout();
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PropertyGrid PG_Settings;
private System.Windows.Forms.Button B_Export;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.ComboBox CB_Namer;
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms;
using PKHeX.Core;
namespace PKHeX.WinForms;
public partial class BoxExporter : Form
{
private readonly SaveFile SAV;
private readonly IFileNamer<PKM>[] Namers = [.. EntityFileNamer.AvailableNamers];
private BoxExportSettings Settings
{
get => (BoxExportSettings)PG_Settings.SelectedObject;
init => PG_Settings.SelectedObject = value;
}
public BoxExporter(SaveFile sav, ExportOverride eo = ExportOverride.None)
{
InitializeComponent();
Icon = Properties.Resources.Icon;
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
SAV = sav;
var obj = Main.Settings.SlotExport;
var settings = obj.BoxExport;
if (eo != 0)
settings = settings with { Scope = eo == ExportOverride.All ? BoxExportScope.All : BoxExportScope.Current };
Settings = settings;
int index = 0;
for (var i = 0; i < Namers.Length; i++)
{
var name = Namers[i].Name;
CB_Namer.Items.Add(name);
if (name == obj.DefaultBoxExportNamer)
index = i;
}
CB_Namer.SelectedIndex = index;
}
private void B_Export_Click(object sender, EventArgs e)
{
if (!TryGetFolder(out var folder))
return;
var namer = GetSelectedNamer();
var settings = Settings;
int ctr = BoxExport.Export(SAV, folder, namer, settings);
if (settings.Notify == BoxExportNofify.Silent)
{
System.Media.SystemSounds.Asterisk.Play();
return;
}
if (ctr < 0)
{
WinFormsUtil.Error(MessageStrings.MsgSaveBoxExportInvalid);
return;
}
var result = string.Format(MessageStrings.MsgSaveBoxExportPathCount, ctr) + Environment.NewLine + folder;
WinFormsUtil.Alert(result);
}
private IFileNamer<PKM> GetSelectedNamer() => Namers[CB_Namer.SelectedIndex];
private bool TryGetFolder([NotNullWhen(true)] out string? folder)
{
using var fbd = new FolderBrowserDialog();
fbd.Description = "Select a folder to export the boxes to.";
fbd.ShowNewFolderButton = true;
var result = fbd.ShowDialog(this);
folder = fbd.SelectedPath;
return result == DialogResult.OK;
}
private void BoxExporter_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason != CloseReason.UserClosing)
return;
var obj = Main.Settings.SlotExport;
obj.DefaultBoxExportNamer = GetSelectedNamer().Name;
obj.BoxExport = Settings;
}
public enum ExportOverride
{
None = 0,
All = 1,
Current = 2,
}
}