mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-23 19:06:10 -05:00
using keyboard instead of mouse can change box without moving mouse outside the slot picturebox trigger an update when the box is changed, but only if the updating parent has the hovered child (ie a boxviewer changing box while hovering the main window won't re-cry the main window slot since it wasn't updated) Closes #1940 add some gender threshold properties to personalinfo
514 lines
19 KiB
C#
514 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Media;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using PKHeX.Core;
|
|
using PKHeX.WinForms.Properties;
|
|
|
|
namespace PKHeX.WinForms.Controls
|
|
{
|
|
/// <summary>
|
|
/// Manager class for moving slots.
|
|
/// </summary>
|
|
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<BoxEditor> Boxes = new List<BoxEditor>();
|
|
public readonly List<ISlotViewer<PictureBox>> OtherSlots = new List<ISlotViewer<PictureBox>>();
|
|
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)
|
|
{
|
|
SE = se;
|
|
Reset();
|
|
}
|
|
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;
|
|
OriginalBackground = pb.BackgroundImage;
|
|
pb.BackgroundImage = CurrentBackground = pb.BackgroundImage == null ? Resources.slotHover : ImageUtil.LayerImage(pb.BackgroundImage, Resources.slotHover, 0, 0, 1);
|
|
BeginHoverSlot(pb);
|
|
}
|
|
private void BeginHoverSlot(PictureBox pb)
|
|
{
|
|
var view = WinFormsUtil.FindFirstControlOfType<ISlotViewer<PictureBox>>(pb);
|
|
var data = view.GetSlotData(pb);
|
|
var pk = SAV.GetStoredSlot(data.Offset);
|
|
HoveredSlot = pb;
|
|
|
|
if (Settings.Default.HoverSlotShowText)
|
|
ShowSimulatorSetTooltip(pb, pk);
|
|
if (Settings.Default.HoverSlotPlayCry)
|
|
PlayCry(pk);
|
|
}
|
|
private void EndHoverSlot()
|
|
{
|
|
HoveredSlot = null;
|
|
ShowSet.RemoveAll();
|
|
Sounds.Stop();
|
|
}
|
|
public void RefreshHoverSlot(ISlotViewer<PictureBox> 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<ISlotViewer<PictureBox>>(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<ISlotViewer<PictureBox>>(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();
|
|
else
|
|
ShowSet.SetToolTip(pb, pk.ShowdownText);
|
|
}
|
|
private void PlayCry(PKM pk)
|
|
{
|
|
if (pk.Species == 0)
|
|
return;
|
|
|
|
string resource = $"{pk.Species}-{pk.AltForm}";
|
|
var path = Path.Combine("sounds", $"{resource}.wav");
|
|
if (!File.Exists(path))
|
|
{
|
|
resource = $"{pk.Species}";
|
|
path = Path.Combine("sounds", $"{resource}.wav");
|
|
if (!File.Exists(path))
|
|
return;
|
|
}
|
|
|
|
Sounds.SoundLocation = path;
|
|
try { Sounds.Play(); } catch { }
|
|
}
|
|
|
|
private static ISlotViewer<T> GetViewParent<T>(T pb) where T : Control
|
|
=> WinFormsUtil.FindFirstControlOfType<ISlotViewer<T>>(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.OriginalData = SAV.GetData(DragInfo.Source.Offset, SAV.SIZE_STORED);
|
|
|
|
// 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?
|
|
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).ConfigureAwait(false);
|
|
if (File.Exists(path) && DragInfo.CurrentPath == null)
|
|
File.Delete(path);
|
|
}
|
|
private string CreateDragDropPKM(PictureBox pb, 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;
|
|
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("Unable to set to locked slot.");
|
|
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("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)
|
|
{
|
|
foreach (var boxview in Boxes)
|
|
updateView(boxview);
|
|
foreach (var other in OtherSlots)
|
|
updateView(other);
|
|
|
|
void updateView(ISlotViewer<PictureBox> 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;
|
|
}
|
|
|
|
// 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);
|
|
|
|
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();
|
|
Sounds?.Dispose();
|
|
}
|
|
}
|
|
}
|