using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Media;
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;
public DrawConfig Draw { private get; set; }
public bool GlowHover { get; set; } = true;
public readonly BitmapAnimator HoverWorker;
private SaveFile SAV => SE.SAV;
public SlotChangeInfo DragInfo;
public readonly List Boxes = new List();
public readonly List> OtherSlots = new List>();
public event DragEventHandler RequestExternalDragDrop;
private readonly ToolTip ShowSet = new ToolTip {InitialDelay = 200, IsBalloon = false};
private readonly SoundPlayer Sounds = new SoundPlayer();
private PictureBox HoveredSlot;
public SlotChangeManager(SAVEditor se)
{
HoverWorker = new BitmapAnimator(Resources.slotHover);
SE = se;
}
public void Reset() { DragInfo = new SlotChangeInfo(SAV); ColorizedBox = ColorizedSlot = -1; }
public bool CanStartDrag => DragInfo.LeftMouseIsDown && !Cursor.Position.Equals(MouseDownPosition);
private Point MouseDownPosition { get; set; }
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;
BeginHoverSlot(pb);
}
private void BeginHoverSlot(PictureBox pb)
{
var view = WinFormsUtil.FindFirstControlOfType>(pb);
var data = view.GetSlotData(pb);
var pk = SAV.GetStoredSlot(data.Offset);
HoveredSlot = pb;
OriginalBackground = pb.BackgroundImage;
Bitmap hover;
if (GlowHover)
{
HoverWorker.Stop();
SpriteUtil.GetSpriteGlow(pk, Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R, out var glowdata, out var GlowBase);
hover = ImageUtil.LayerImage(GlowBase, Resources.slotHover, 0, 0);
HoverWorker.GlowToColor = Draw.GlowFinal;
HoverWorker.GlowFromColor = Draw.GlowInitial;
HoverWorker.Start(pb, GlowBase, glowdata, OriginalBackground);
}
else
{
hover = Resources.slotHover;
}
pb.BackgroundImage = CurrentBackground = OriginalBackground == null ? hover : ImageUtil.LayerImage(OriginalBackground, hover, 0, 0);
if (Settings.Default.HoverSlotShowText)
ShowSimulatorSetTooltip(pb, pk);
if (Settings.Default.HoverSlotPlayCry)
PlayCry(pk);
}
private void EndHoverSlot()
{
if (HoveredSlot != null)
HoverCancel();
ShowSet.RemoveAll();
Sounds.Stop();
}
public void HoverCancel()
{
HoverWorker.Stop();
HoveredSlot = null;
}
public void RefreshHoverSlot(ISlotViewer parent)
{
if (HoveredSlot == null || !parent.SlotPictureBoxes.Contains(HoveredSlot))
return;
BeginHoverSlot(HoveredSlot);
}
public void MouseLeave(object sender, EventArgs e)
{
EndHoverSlot();
var pb = (PictureBox)sender;
if (pb.BackgroundImage != CurrentBackground)
return;
pb.BackgroundImage = OriginalBackground;
}
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;
MouseDownPosition = Cursor.Position;
}
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 MouseMove(object sender, MouseEventArgs e)
{
if (!CanStartDrag)
return;
// Abort if there is no Pokemon in the given slot.
PictureBox pb = (PictureBox)sender;
if (pb.Image == null)
return;
var view = WinFormsUtil.FindFirstControlOfType>(pb);
var src = view.GetSlotData(pb);
if (!src.Editable || SAV.IsSlotLocked(src.Box, src.Slot))
return;
bool encrypt = Control.ModifierKeys == Keys.Control;
HandleMovePKM(pb, encrypt);
}
public void DragDrop(object sender, DragEventArgs e)
{
PictureBox pb = (PictureBox)sender;
var view = WinFormsUtil.FindFirstControlOfType>(pb);
var src = view.GetSlotData(pb);
if (!src.Editable || SAV.IsSlotLocked(src.Box, src.Slot))
{
SystemSounds.Asterisk.Play();
e.Effect = DragDropEffects.Copy;
DragInfo.Reset();
return;
}
bool overwrite = Control.ModifierKeys == Keys.Alt;
bool clone = Control.ModifierKeys == Keys.Control;
DragInfo.Destination = src;
HandleDropPKM(sender, e, overwrite, clone);
}
private void ShowSimulatorSetTooltip(Control pb, PKM pk)
{
if (pk.Species == 0)
{
ShowSet.RemoveAll();
return;
}
var text = GetLocalizedPreviewText(pk, Settings.Default.Language);
ShowSet.SetToolTip(pb, text);
}
private void PlayCry(PKM pk)
{
if (pk.Species == 0)
return;
string path = GetCryPath(pk, Main.CryPath);
if (!File.Exists(path))
return;
Sounds.SoundLocation = path;
try { Sounds.Play(); }
catch { }
}
private static ISlotViewer GetViewParent(T pb) where T : Control
=> WinFormsUtil.FindFirstControlOfType>(pb);
public void HandleMovePKM(PictureBox pb, bool encrypt)
{
// Create a temporary PKM file to perform a drag drop operation.
// Set flag to prevent re-entering.
DragInfo.DragDropInProgress = true;
// Prepare Data
DragInfo.Source = GetViewParent(pb).GetSlotData(pb);
DragInfo.Source.PKM = SAV.GetStoredSlot(DragInfo.Source.Offset);
// Make a new file name based off the PID
string newfile = CreateDragDropPKM(pb, 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?
// Keep it to 10 seconds; Discord upload only stores the file path until you click Upload.
int delay = external ? 10_000 : 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).ConfigureAwait(true);
if (File.Exists(path) && DragInfo.CurrentPath == null)
File.Delete(path);
}
private string CreateDragDropPKM(PictureBox pb, bool encrypt, out bool external)
{
// Make File
PKM pk = DragInfo.Source.PKM;
string newfile = FileUtil.GetPKMTempFileName(pk, encrypt);
try
{
TryMakeDragDropPKM(pb, encrypt, pk, newfile, out external);
}
catch (Exception x)
{
WinFormsUtil.Error("Drag & Drop Error", x);
external = false;
}
return newfile;
}
private bool TryMakeDragDropPKM(PictureBox pb, bool encrypt, PKM pk, string newfile, out bool external)
{
File.WriteAllBytes(newfile, encrypt ? pk.EncryptedBoxData : pk.DecryptedBoxData);
var img = (Bitmap)pb.Image;
SetCursor(new Cursor(img.GetHicon()), pb);
HoverCancel();
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;
SetCursor(SE.GetDefaultCursor, pb);
return false;
}
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)
{
var pb = (PictureBox)sender;
DragInfo.Destination = GetViewParent(pb).GetSlotData(pb);
// 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(MessageStrings.MsgSaveSlotLocked);
return;
}
bool noEgg = DragInfo.Destination.IsParty && SE.SAV.IsPartyAllEggs(DragInfo.Destination.Slot) && !SE.HaX;
if (DragInfo.Source.Offset < 0) // external source
{
if (!TryLoadFiles(files, e, noEgg))
AlertInvalidate(MessageStrings.MsgSaveSlotBadData);
return;
}
if (!TrySetPKMDestination(sender, e, overwrite, clone, noEgg))
{
AlertInvalidate(MessageStrings.MsgSaveSlotEmpty);
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(IReadOnlyList files, DragEventArgs e, bool noEgg)
{
if (files.Count == 0)
return false;
var temp = FileUtil.GetSingleFromPath(files[0], SAV);
if (temp == null)
{
RequestExternalDragDrop?.Invoke(this, e); // pass thru
return true; // treat as handled
}
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;
if (SAV is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(pk.Format, il.Japanese, pk.Japanese))
{
c = PKMConverter.GetIncompatibleGBMessage(pk, il.Japanese);
WinFormsUtil.Error(c);
Debug.WriteLine(c);
return false;
}
var errata = SAV.IsPKMCompatible(pk);
if (errata.Count > 0)
{
string concat = string.Join(Environment.NewLine, errata);
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, concat, MessageStrings.MsgContinue))
{
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);
return true;
}
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);
return true;
}
return false;
}
public void SetColor(int box, int slot, Image img)
{
foreach (var boxview in Boxes)
updateView(boxview);
foreach (var other in OtherSlots)
updateView(other);
void updateView(ISlotViewer view)
{
if (view.ViewIndex == ColorizedBox && ColorizedSlot >= 0)
view.SlotPictureBoxes[ColorizedSlot].BackgroundImage = null;
if (view.ViewIndex == box && slot >= 0)
view.SlotPictureBoxes[slot].BackgroundImage = img;
}
ColorizedBox = box;
ColorizedSlot = slot;
ColorizedColor = img;
OriginalBackground = img;
if (HoverWorker != null)
HoverWorker.OriginalBackground = 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 = SAV.PartyCount;
SetColor(slot.Box, slot.Slot, img ?? Resources.slotSet);
return;
}
int o = slot.Offset;
SAV.SetStoredSlot(pk, o);
if (slot.Type == StorageSlotType.Box)
{
foreach (var boxview in Boxes)
{
if (boxview.CurrentBox != slot.Box)
continue;
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);
SE.SetParty();
return;
}
}
else
{
if (SAV.PartyCount < slot.Slot)
{
o = SAV.GetPartyOffset(SAV.PartyCount);
slot.Slot = SAV.PartyCount;
}
}
SAV.SetPartySlot(pk, o);
SE.SetParty();
}
// Utility
public void SwapBoxes(int index, int other)
{
if (index == other)
return;
SAV.SwapBox(index, other);
UpdateBoxViewAtBoxIndexes(index, other);
}
public void Dispose()
{
Sounds.Dispose();
HoverWorker.Dispose();
SE?.Dispose();
OriginalBackground?.Dispose();
CurrentBackground?.Dispose();
ColorizedColor?.Dispose();
}
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);
}
}
private static string GetCryPath(PKM pk, string cryFolder)
{
var name = PKX.GetResourceStringSprite(pk.Species, pk.AltForm, pk.Gender, pk.Format).Replace('_', '-').Substring(1);
var path = Path.Combine(cryFolder, $"{name}.wav");
if (!File.Exists(path))
path = Path.Combine(cryFolder, $"{pk.Species}.wav");
return path;
}
public static string GetLocalizedPreviewText(PKM pk, string language)
{
var set = new ShowdownSet(pk);
if (pk.Format <= 2) // Nature preview from IVs
set.Nature = Experience.GetNatureVC(pk.EXP);
return set.LocalizedText(language);
}
}
}