using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Threading.Tasks; using System.Windows.Forms; using PKHeX.Core; using PKHeX.WinForms.Properties; namespace PKHeX.WinForms.Controls { /// /// Manager class for moving slots. /// public sealed class SlotChangeManager : IDisposable { // Disposeables public readonly SAVEditor SE; private Image OriginalBackground; private Image CurrentBackground; public Image ColorizedColor { get; private set; } public int ColorizedBox { get; private set; } = -1; public int ColorizedSlot { get; private set; } = -1; private SaveFile SAV => SE.SAV; public SlotChangeInfo DragInfo; public readonly List Boxes = new List(); public event DragEventHandler RequestExternalDragDrop; public SlotChangeManager(SAVEditor se) { SE = se; Reset(); } public void Reset() { DragInfo = new SlotChangeInfo(SAV); ColorizedBox = ColorizedSlot = -1; } public bool DragActive => DragInfo.DragDropInProgress || !DragInfo.LeftMouseIsDown; public void SetCursor(Cursor z, object sender) { if (SE != null) DragInfo.Cursor = ((Control)sender).FindForm().Cursor = z; } public void MouseEnter(object sender, EventArgs e) { var pb = (PictureBox)sender; if (pb.Image == null) return; OriginalBackground = pb.BackgroundImage; pb.BackgroundImage = CurrentBackground = pb.BackgroundImage == null ? Resources.slotHover : ImageUtil.LayerImage(pb.BackgroundImage, Resources.slotHover, 0, 0, 1); if (!DragActive) SetCursor(Cursors.Hand, sender); } public void MouseLeave(object sender, EventArgs e) { var pb = (PictureBox)sender; if (pb.BackgroundImage != CurrentBackground) return; pb.BackgroundImage = OriginalBackground; if (!DragActive) SetCursor(Cursors.Default, sender); } public void MouseClick(object sender, MouseEventArgs e) { if (!DragInfo.DragDropInProgress) SE.ClickSlot(sender, e); } public void MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) DragInfo.LeftMouseIsDown = false; if (e.Button == MouseButtons.Right) DragInfo.RightMouseIsDown = false; } public void MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) DragInfo.LeftMouseIsDown = true; if (e.Button == MouseButtons.Right) DragInfo.RightMouseIsDown = true; } public void QueryContinueDrag(object sender, QueryContinueDragEventArgs e) { if (e.Action != DragAction.Cancel && e.Action != DragAction.Drop) return; DragInfo.LeftMouseIsDown = false; DragInfo.RightMouseIsDown = false; DragInfo.DragDropInProgress = false; } public void DragEnter(object sender, DragEventArgs e) { if (e.AllowedEffect == (DragDropEffects.Copy | DragDropEffects.Link)) // external file e.Effect = DragDropEffects.Copy; else if (e.Data != null) // within e.Effect = DragDropEffects.Move; if (DragInfo.DragDropInProgress) SetCursor((Cursor)DragInfo.Cursor, sender); } public void HandleMovePKM(PictureBox pb, int slot, int box, bool encrypt) { // Create a temporary PKM file to perform a drag drop operation. // Set flag to prevent re-entering. DragInfo.DragDropInProgress = true; DragInfo.Source.Parent = pb.Parent; DragInfo.Source.Slot = slot; DragInfo.Source.Box = box; DragInfo.Source.Offset = SE.GetPKMOffset(DragInfo.Source.Slot, DragInfo.Source.Box); // Prepare Data DragInfo.Source.OriginalData = SAV.GetData(DragInfo.Source.Offset, SAV.SIZE_STORED); // Make a new file name based off the PID string newfile = CreateDragDropPKM(pb, box, encrypt, out bool external); DragInfo.Reset(); SetCursor(SE.GetDefaultCursor, pb); // 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? int delay = external ? 500 : 0; DeleteAsync(newfile, delay); if (DragInfo.Source.IsParty || DragInfo.Destination.IsParty) SE.SetParty(); } private async void DeleteAsync(string path, int delay) { await Task.Delay(delay); if (File.Exists(path) && DragInfo.CurrentPath == null) File.Delete(path); } private string CreateDragDropPKM(PictureBox pb, int box, bool encrypt, out bool external) { byte[] dragdata = SAV.DecryptPKM(DragInfo.Source.OriginalData); Array.Resize(ref dragdata, SAV.SIZE_STORED); PKM pkx = SAV.GetPKM(dragdata); string fn = pkx.FileName; fn = fn.Substring(0, fn.LastIndexOf('.')); string filename = $"{fn}{(encrypt ? $".ek{pkx.Format}" : $".{pkx.Extension}")}"; // Make File string newfile = Path.Combine(Path.GetTempPath(), Util.CleanFileName(filename)); try { TryMakeDragDropPKM(pb, encrypt, pkx, newfile, out external); } catch (Exception x) { WinFormsUtil.Error("Drag & Drop Error", x); external = false; } return newfile; } private bool TryMakeDragDropPKM(PictureBox pb, bool encrypt, PKM pkx, string newfile, out bool external) { File.WriteAllBytes(newfile, encrypt ? pkx.EncryptedBoxData : pkx.DecryptedBoxData); var img = (Bitmap)pb.Image; SetCursor(new Cursor(img.GetHicon()), pb); pb.Image = null; pb.BackgroundImage = Resources.slotDrag; // Thread Blocks on DoDragDrop DragInfo.CurrentPath = newfile; DragDropEffects result = pb.DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Move); external = !DragInfo.Source.IsValid || result != DragDropEffects.Link; if (external || DragInfo.SameSlot || result != DragDropEffects.Link) // not dropped to another box slot, restore img { pb.Image = img; pb.BackgroundImage = OriginalBackground; } if (result == DragDropEffects.Copy) // viewed in tabs or cloned { if (!DragInfo.Destination.IsValid) // apply 'view' highlight SetColor(DragInfo.Source.Box, DragInfo.Source.Slot, Resources.slotView); external = false; } return true; } private void SetSlotSprite(SlotChange loc, PKM pk, BoxEditor x = null) => (x ?? SE.Box).SetSlotFiller(pk, loc.Box, loc.Slot); public void HandleDropPKM(object sender, DragEventArgs e, bool overwrite, bool clone) { DragInfo.Destination.Offset = SE.GetPKMOffset(DragInfo.Destination.Slot, DragInfo.Destination.Box); // Check for In-Dropped files (PKX,SAV,ETC) string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); if (Directory.Exists(files[0])) { SE.LoadBoxes(out string _, files[0]); return; } if (DragInfo.SameSlot) { e.Effect = DragDropEffects.Link; return; } if (SAV.IsSlotLocked(DragInfo.Destination.Box, DragInfo.Destination.Slot)) { AlertInvalidate("Unable to set to locked slot."); return; } bool noEgg = DragInfo.Destination.IsParty && SE.SAV.IsPartyAllEggs(DragInfo.Destination.Slot - 30) && !SE.HaX; if (DragInfo.Source.Offset < 0) // external source { if (!TryLoadFiles(files, e, noEgg)) AlertInvalidate("Unable to set to this slot."); return; } if (!TrySetPKMDestination(sender, e, overwrite, clone, noEgg)) { AlertInvalidate("Unable to set to this slot."); return; } if (DragInfo.Source.Parent == null) // internal file DragInfo.Reset(); } private void AlertInvalidate(string msg) { DragInfo.Destination.Slot = -1; // Invalidate WinFormsUtil.Alert(msg); } private bool TryLoadFiles(string[] files, DragEventArgs e, bool noEgg) { if (files.Length <= 0) return false; string file = files[0]; FileInfo fi = new FileInfo(file); if (!fi.Exists) return false; if (!PKX.IsPKM(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length)) { RequestExternalDragDrop?.Invoke(this, e); // pass thru return false; } byte[] data = File.ReadAllBytes(file); MysteryGift mg = MysteryGift.GetMysteryGift(data, fi.Extension); if (fi.Extension.Length > 0 || !int.TryParse(fi.Extension[fi.Extension.Length - 1].ToString(), out var prefer)) prefer = SAV.Generation; PKM temp = mg?.ConvertToPKM(SAV) ?? PKMConverter.GetPKMfromBytes(data, prefer: prefer); PKM pk = PKMConverter.ConvertToType(temp, SAV.PKMType, out string c); if (pk == null) { WinFormsUtil.Error(c); Debug.WriteLine(c); return false; } if (noEgg && (pk.Species == 0 || pk.IsEgg)) return false; string[] errata = SAV.IsPKMCompatible(pk); if (errata.Length > 0) { string concat = string.Join(Environment.NewLine, errata); if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, concat, "Continue?")) { Debug.WriteLine(c); Debug.WriteLine(concat); return false; } } SetPKM(pk, false, Resources.slotSet); Debug.WriteLine(c); return true; } private bool TrySetPKMDestination(object sender, DragEventArgs e, bool overwrite, bool clone, bool noEgg) { PKM pkz = GetPKM(true); if (noEgg && (pkz.Species == 0 || pkz.IsEgg)) return false; if (DragInfo.Source.IsValid) TrySetPKMSource(sender, overwrite, clone); // Copy from temp to destination slot. SetPKM(pkz, false, null); e.Effect = clone ? DragDropEffects.Copy : DragDropEffects.Link; SetCursor(SE.GetDefaultCursor, sender); return true; } private bool TrySetPKMSource(object sender, bool overwrite, bool clone) { if (overwrite && DragInfo.Destination.IsValid) // overwrite delete old slot { // Clear from slot SetPKM(SAV.BlankPKM, true, null); } else if (!clone && DragInfo.Destination.IsValid) { // Load data from destination PKM pk = ((PictureBox)sender).Image != null ? GetPKM(false) : SAV.BlankPKM; // Set destination pokemon data to source slot SetPKM(pk, true, null); } else return false; return true; } public void SetColor(int box, int slot, Image img) { // Update SubViews for (int b = 0; b < Boxes.Count; b++) { var boxview = Boxes[b]; if (boxview.CurrentBox != box) { if (b > 0 || slot < 30) { foreach (var s in boxview.SlotPictureBoxes) s.BackgroundImage = null; continue; } } var slots = boxview.SlotPictureBoxes; for (int i = 0; i < slots.Count; i++) slots[i].BackgroundImage = slot == i ? img : null; } ColorizedBox = box; ColorizedSlot = slot; ColorizedColor = img; } // PKM Get Set private PKM GetPKM(bool src) => GetPKM(src ? DragInfo.Source : DragInfo.Destination); public PKM GetPKM(SlotChange slot) { int o = slot.Offset; if (o < 0) return slot.PKM; if (slot.IsParty) return SAV.GetPartySlot(o); var pk = SAV.GetStoredSlot(o); pk.Slot = slot.Slot; pk.Box = slot.Box; return pk; } private void SetPKM(PKM pk, bool src, Image img) => SetPKM(pk, src ? DragInfo.Source : DragInfo.Destination, src, img); public void SetPKM(PKM pk, SlotChange slot, bool src, Image img) { if (slot.IsParty) { SetPKMParty(pk, src, slot); if (img == Resources.slotDel) slot.Slot = 30 + SAV.PartyCount; SetColor(slot.Box, slot.Slot, img ?? Resources.slotSet); return; } int o = slot.Offset; SAV.SetStoredSlot(pk, o); if (slot.Slot >= 30) { SetSlotSprite(slot, pk); return; } // Update SubViews foreach (var boxview in Boxes) { if (boxview.CurrentBox == slot.Box) { Debug.WriteLine($"Setting to {boxview.Parent.Name}'s [{boxview.CurrentBox+1:d2}]|{boxview.CurrentBoxName} at Slot {slot.Slot+1}."); SetSlotSprite(slot, pk, boxview); } } SetColor(slot.Box, slot.Slot, img ?? Resources.slotSet); } private void SetPKMParty(PKM pk, bool src, SlotChange slot) { int o = slot.Offset; if (src) { if (pk.Species == 0) // Empty Slot { SAV.DeletePartySlot(slot.Slot - 30); SE.SetParty(); return; } } else { if (30 + SAV.PartyCount < slot.Slot) { o = SAV.GetPartyOffset(SAV.PartyCount); slot.Slot = 30 + SAV.PartyCount; } } SAV.SetPartySlot(pk, o); SE.SetParty(); } // Utility public void SwapBoxes(int index, int other) { if (index == other) return; SAV.SwapBox(index, other); foreach (var box in Boxes) { if (box.CurrentBox != index && box.CurrentBox != other) continue; box.ResetSlots(); box.ResetBoxNames(box.CurrentBox); } } public void Dispose() { SE?.Dispose(); OriginalBackground?.Dispose(); CurrentBackground?.Dispose(); ColorizedColor?.Dispose(); } } }