PKHeX/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs

396 lines
13 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
2018-09-16 19:44:00 +00:00
using System.Linq;
using System.Media;
using System.Threading.Tasks;
using System.Windows.Forms;
using PKHeX.Core;
using PKHeX.Drawing.PokeSprite;
namespace PKHeX.WinForms.Controls
{
/// <summary>
/// Orchestrates the movement of slots within the GUI.
/// </summary>
public sealed class SlotChangeManager : IDisposable
{
public readonly SAVEditor SE;
public readonly SlotTrackerImage LastSlot = new();
public readonly DragManager Drag = new();
public SaveDataEditor<PictureBox> Env { get; set; } = null!;
public readonly List<BoxEditor> Boxes = new();
public readonly SlotHoverHandler Hover = new();
public SlotChangeManager(SAVEditor se) => SE = se;
public void Reset()
{
Drag.Initialize();
LastSlot.Reset();
}
public void MouseEnter(object? sender, EventArgs e)
{
if (sender is not PictureBox pb)
return;
bool dataPresent = pb.Image is not null;
if (dataPresent)
Hover.Start(pb, LastSlot);
pb.Cursor = dataPresent ? Cursors.Hand : Cursors.Default;
}
public void MouseLeave(object? sender, EventArgs e)
{
Hover.Stop();
}
public void MouseClick(object? sender, MouseEventArgs e)
{
if (sender == null)
return;
if (!Drag.Info.DragDropInProgress)
SE.ClickSlot(sender, e);
}
public void MouseUp(object? sender, MouseEventArgs e)
{
if (sender == null)
return;
if (e.Button == MouseButtons.Left)
Drag.Info.LeftMouseIsDown = false;
Drag.Info.Source = null;
}
public void MouseDown(object? sender, MouseEventArgs e)
{
if (sender == null)
return;
if (e.Button == MouseButtons.Left)
{
Drag.Info.LeftMouseIsDown = true;
Drag.MouseDownPosition = Cursor.Position;
}
}
public void QueryContinueDrag(object? sender, QueryContinueDragEventArgs e)
{
if (sender == null)
return;
if (e.Action != DragAction.Cancel && e.Action != DragAction.Drop)
return;
Drag.Info.LeftMouseIsDown = false;
Drag.Info.DragDropInProgress = false;
}
public void DragEnter(object? sender, DragEventArgs e)
{
if (sender == null)
return;
if ((e.AllowedEffect & DragDropEffects.Copy) != 0) // external file
e.Effect = DragDropEffects.Copy;
else if (e.Data != null) // within
e.Effect = DragDropEffects.Move;
if (Drag.Info.DragDropInProgress)
Drag.SetCursor(((Control)sender).FindForm(), Drag.Info.Cursor);
}
private static SlotViewInfo<T> GetSlotInfo<T>(T pb) where T : Control
{
var view = WinFormsUtil.FindFirstControlOfType<ISlotViewer<T>>(pb);
if (view == null)
throw new InvalidCastException("Unable to find View Parent");
var src = view.GetSlotData(pb);
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
return new SlotViewInfo<T>(src, view);
}
public void MouseMove(object? sender, MouseEventArgs e)
{
if (sender == null)
return;
if (!Drag.CanStartDrag)
return;
// Abort if there is no Pokemon in the given slot.
PictureBox pb = (PictureBox)sender;
if (pb.Image == null)
return;
bool encrypt = Control.ModifierKeys == Keys.Control;
HandleMovePKM(pb, encrypt);
}
public void DragDrop(object? sender, DragEventArgs e)
{
if (sender == null)
return;
PictureBox pb = (PictureBox)sender;
var info = GetSlotInfo(pb);
if (!info.CanWriteTo() || Drag.Info.Source?.CanWriteTo() == false)
{
SystemSounds.Asterisk.Play();
e.Effect = DragDropEffects.Copy;
Drag.Reset();
return;
}
var mod = SlotUtil.GetDropModifier();
Drag.Info.Destination = info;
HandleDropPKM(pb, e, mod);
}
private void HandleMovePKM(PictureBox pb, bool encrypt)
{
// Create a temporary PKM file to perform a drag drop operation.
// Set flag to prevent re-entering.
Drag.Info.DragDropInProgress = true;
// Prepare Data
Drag.Info.Source = GetSlotInfo(pb);
Drag.Info.Destination = null;
// Make a new file name based off the PID
string newfile = CreateDragDropPKM(pb, encrypt, out bool external);
// drop finished, clean up
Drag.Info.Source = null;
Drag.Reset();
Drag.ResetCursor(pb.FindForm());
// Browser apps need time to load data since the file isn't moved to a location on the user's local storage.
// Tested 10ms -> too quick, 100ms was fine. 500ms should be safe?
// Keep it to 20 seconds; Discord upload only stores the file path until you click Upload.
int delay = external ? 20_000 : 0;
DeleteAsync(newfile, delay);
if (Drag.Info.DragIsParty)
SE.SetParty();
}
private async void DeleteAsync(string path, int delay)
{
await Task.Delay(delay).ConfigureAwait(true);
if (!File.Exists(path) || Drag.Info.CurrentPath == path)
return;
try { File.Delete(path); }
catch (Exception ex) { Debug.WriteLine(ex.Message); }
}
private string CreateDragDropPKM(PictureBox pb, bool encrypt, out bool external)
{
// Make File
var pk = Drag.Info.Source!.ReadCurrent();
2019-02-15 19:46:46 +00:00
string newfile = FileUtil.GetPKMTempFileName(pk, encrypt);
try
{
var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData;
external = TryMakeDragDropPKM(pb, data, newfile);
}
// Tons of things can happen with drag & drop; don't try to handle things, just indicate failure.
catch (Exception x)
{
WinFormsUtil.Error("Drag && Drop Error", x);
external = false;
}
return newfile;
}
private bool TryMakeDragDropPKM(PictureBox pb, byte[] data, string newfile)
{
File.WriteAllBytes(newfile, data);
var img = (Bitmap)pb.Image;
Drag.SetCursor(pb.FindForm(), new Cursor(img.GetHicon()));
Hover.Stop();
pb.Image = null;
2019-11-16 01:34:18 +00:00
pb.BackgroundImage = SpriteUtil.Spriter.Drag;
// Thread Blocks on DoDragDrop
Drag.Info.CurrentPath = newfile;
var result = pb.DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Copy);
var external = Drag.Info.Destination == null || result != DragDropEffects.Link;
if (external || Drag.Info.SameLocation) // not dropped to another box slot, restore img
{
pb.Image = img;
pb.BackgroundImage = LastSlot.OriginalBackground;
Drag.ResetCursor(pb.FindForm());
return external;
}
if (result == DragDropEffects.Copy) // viewed in tabs or cloned
{
if (Drag.Info.Destination == null) // apply 'view' highlight
Env.Slots.Get(Drag.Info.Source!.Slot);
return false;
}
return true;
}
2021-12-05 06:29:51 +00:00
private void HandleDropPKM(PictureBox pb, DragEventArgs? e, DropModifier mod)
{
2021-12-05 06:29:51 +00:00
if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] {Length: not 0} files)
{
Drag.Reset();
return;
}
if (Directory.Exists(files[0])) // folder
{
SE.LoadBoxes(out string _, files[0]);
Drag.Reset();
return;
}
e.Effect = mod == DropModifier.Clone ? DragDropEffects.Copy : DragDropEffects.Link;
// file
if (Drag.Info.SameLocation)
{
e.Effect = DragDropEffects.Link;
return;
}
var dest = Drag.Info.Destination;
if (Drag.Info.Source == null) // external source
{
bool badDest = !dest!.CanWriteTo();
if (!TryLoadFiles(files, e, badDest))
WinFormsUtil.Alert(MessageStrings.MsgSaveSlotBadData);
}
else if (!TrySetPKMDestination(pb, mod))
{
WinFormsUtil.Alert(MessageStrings.MsgSaveSlotEmpty);
}
Drag.Reset();
}
/// <summary>
/// Tries to load the input <see cref="files"/>
/// </summary>
/// <param name="files">Files to load</param>
/// <param name="e">Args</param>
/// <param name="badDest">Destination slot disallows eggs/blanks</param>
/// <returns>True if loaded</returns>
private bool TryLoadFiles(IReadOnlyList<string> files, DragEventArgs e, bool badDest)
{
2018-09-16 19:44:00 +00:00
if (files.Count == 0)
return false;
2018-09-16 19:44:00 +00:00
var sav = Drag.Info.Destination!.View.SAV;
var path = files[0];
var temp = FileUtil.GetSingleFromPath(path, sav);
2018-09-16 19:44:00 +00:00
if (temp == null)
{
Drag.RequestDD(this, e); // pass thru
return true; // treat as handled
}
var pk = EntityConverter.ConvertToType(temp, sav.PKMType, out var result);
if (pk == null)
{
var c = result.GetDisplayString(temp, sav.PKMType);
WinFormsUtil.Error(c);
Debug.WriteLine(c);
return false;
}
if (badDest && (pk.Species == 0 || pk.IsEgg))
return false;
if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese))
{
var str = EntityConverterResult.IncompatibleLanguageGB.GetIncompatibleGBMessage(pk, il.Japanese);
WinFormsUtil.Error(str);
Debug.WriteLine(str);
return false;
}
var errata = sav.EvaluateCompatibility(pk);
2018-07-21 04:32:33 +00:00
if (errata.Count > 0)
{
string concat = string.Join(Environment.NewLine, errata);
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, concat, MessageStrings.MsgContinue))
{
Debug.WriteLine(result.GetDisplayString(temp, sav.PKMType));
Debug.WriteLine(concat);
return false;
}
}
Env.Slots.Set(Drag.Info.Destination!.Slot, pk);
Debug.WriteLine(result.GetDisplayString(temp, sav.PKMType));
return true;
}
private bool TrySetPKMDestination(PictureBox pb, DropModifier mod)
{
var info = Drag.Info;
var pk = info.Source!.ReadCurrent();
var msg = Drag.Info.Destination!.CanWriteTo(pk);
if (msg != WriteBlockedMessage.None)
return false;
if (Drag.Info.Source != null)
TrySetPKMSource(pb, mod);
// Copy from temp to destination slot.
var type = info.DragIsSwap ? SlotTouchType.Swap : SlotTouchType.Set;
Env.Slots.Set(info.Destination!.Slot, pk, type);
Drag.ResetCursor(pb.FindForm());
return true;
}
private bool TrySetPKMSource(PictureBox sender, DropModifier mod)
{
var info = Drag.Info;
if (info.Destination == null || mod == DropModifier.Clone)
return false;
if (sender.Image == null || mod == DropModifier.Overwrite)
{
Env.Slots.Delete(info.Source!.Slot);
return true;
}
var type = info.DragIsSwap ? SlotTouchType.Swap : SlotTouchType.Set;
var pk = info.Destination.ReadCurrent();
Env.Slots.Set(Drag.Info.Source!.Slot, pk, type);
return true;
}
// Utility
public void SwapBoxes(int index, int other, SaveFile SAV)
{
if (index == other)
return;
SAV.SwapBox(index, other);
2018-09-16 19:44:00 +00:00
UpdateBoxViewAtBoxIndexes(index, other);
}
public void Dispose()
{
Hover.Dispose();
SE.Dispose();
LastSlot.OriginalBackground?.Dispose();
LastSlot.CurrentBackground?.Dispose();
}
2018-09-16 19:44:00 +00:00
private void UpdateBoxViewAtBoxIndexes(params int[] boxIndexes)
{
foreach (var box in Boxes)
{
var current = box.CurrentBox;
if (!boxIndexes.Contains(current))
continue;
box.ResetSlots();
box.ResetBoxNames(current);
}
}
}
}