using PKHeX.Core; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; namespace PKHeX.WinForms { public static class WinFormsUtil { #region Form Translation private static readonly string[] Splitter = {" = "}; private const char Comment = '-'; private const char FormStart = '!'; internal static void TranslateInterface(Control form, string lang) { if (!TryGetTranslationFile(lang, out string[] rawlist)) return; // no translation data retrieved // Find Starting Point int start = Array.FindIndex(rawlist, z => z.StartsWith($"{FormStart} {form.Name}")); if (start < 0) // no form info found return; // Rename Window Title string[] WindowName = rawlist[start].Split(Splitter, StringSplitOptions.None); if (WindowName.Length > 1) // window title is specified form.Text = WindowName[1]; // Fetch controls to rename var stringdata = GetTranslationList(rawlist, start); if (stringdata.Count == 0) // no translation data available return; // Execute Translation form.SuspendLayout(); TranslateForm(form, stringdata); form.ResumeLayout(); } private static bool TryGetTranslationFile(string lang, out string[] rawlist) { var file = $"lang_{lang}"; // Check to see if a the translation file exists in the same folder as the executable string externalLangPath = $"{file}.txt"; if (File.Exists(externalLangPath)) { try { rawlist = File.ReadAllLines(externalLangPath); return true; } catch { /* In use? Just return the internal resource. */ } } rawlist = Util.GetStringList(file); // If there's no strings (or null), the translation file does not exist. // No file => abort this function and don't translate UI. return rawlist?.Length > 0; } private static List GetTranslationList(IReadOnlyList rawlist, int start) { List stringdata = new List(); for (int i = start + 1; i < rawlist.Count; i++) { var line = rawlist[i]; if (line.Length == 0) continue; // Skip Over Empty Lines if (line[0] == Comment) continue; // Keep translating if line is a comment line if (line[0] == FormStart) // Stop if we have reached the end of translation break; stringdata.Add(line); // Add the entry to process later. } return stringdata; } private static void TranslateForm(Control form, IEnumerable stringdata) { // Only fetch the list of controls once; store in dictionary for faster translation var controls = GetControlDictionary(form); foreach (string str in stringdata) { string[] SplitString = str.Split(Splitter, StringSplitOptions.None); if (SplitString.Length != 2) continue; var controlName = SplitString[0]; if (!controls.TryGetValue(controlName, out object c)) continue; // control not found string text = SplitString[1]; if (c is Control r) r.Text = text; else if (c is ToolStripItem t) t.Text = text; } } private static Dictionary GetControlDictionary(Control form) { return GetTranslatableControls(form) .GroupBy(p => p.Key, StringComparer.OrdinalIgnoreCase) .ToDictionary(g => g.Key, g => g.First().Value, StringComparer.OrdinalIgnoreCase); } private static IEnumerable> GetTranslatableControls(Control f) { foreach (var z in f.GetChildrenOfType()) { switch (z) { case ToolStrip menu: foreach (var pair in GetToolStripMenuItems(menu)) yield return pair; break; default: if (string.IsNullOrWhiteSpace(z.Name)) break; if (z.ContextMenuStrip != null) // control has attached menustrip foreach (var pair in GetToolStripMenuItems(z.ContextMenuStrip)) yield return pair; if (z is ComboBox || z is TextBox || z is MaskedTextBox || z is LinkLabel) break; // undesirable to modify, ignore if (!string.IsNullOrWhiteSpace(z.Text)) yield return new KeyValuePair(z.Name, z); break; } } } private static IEnumerable GetChildrenOfType(this Control control) where T : class { foreach (Control child in control.Controls) { T childOfT = child as T; if (childOfT != null) yield return childOfT; if (!child.HasChildren) continue; foreach (T descendant in GetChildrenOfType(child)) yield return descendant; } } private static IEnumerable> GetToolStripMenuItems(ToolStrip menu) { foreach (var i in menu.Items.OfType()) { if (!string.IsNullOrWhiteSpace(i.Text)) yield return new KeyValuePair(i.Name, i); foreach (var sub in GetToolsStripDropDownItems(i).Where(z => !string.IsNullOrWhiteSpace(z.Text))) yield return new KeyValuePair(sub.Name, sub); } } private static IEnumerable GetToolsStripDropDownItems(ToolStripDropDownItem item) { foreach (var dropDownItem in item.DropDownItems.OfType()) { yield return dropDownItem; if (!dropDownItem.HasDropDownItems) continue; foreach (ToolStripMenuItem subItem in GetToolsStripDropDownItems(dropDownItem)) yield return subItem; } } internal static void CenterToForm(this Control child, Control parent) { int x = parent.Location.X + (parent.Width - child.Width) / 2; int y = parent.Location.Y + (parent.Height - child.Height) / 2; child.Location = new Point(Math.Max(x, 0), Math.Max(y, 0)); } #endregion public static Form FirstFormOfType(this Form f) => f.OwnedForms.FirstOrDefault(form => form is T); public static Control GetUnderlyingControl(object sender) => ((sender as ToolStripItem)?.Owner as ContextMenuStrip)?.SourceControl ?? sender as PictureBox; #region Message Displays /// /// Displays a dialog showing the details of an error. /// /// User-friendly message about the error. /// Instance of the error's . /// The associated with the dialog. internal static DialogResult Error(string friendlyMessage, Exception exception) { System.Media.SystemSounds.Exclamation.Play(); return ErrorWindow.ShowErrorDialog(friendlyMessage, exception, true); } /// /// Displays a dialog showing the details of an error. /// /// User-friendly message about the error. /// The associated with the dialog. internal static DialogResult Error(params string[] lines) { System.Media.SystemSounds.Exclamation.Play(); string msg = string.Join(Environment.NewLine + Environment.NewLine, lines); return MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } internal static DialogResult Alert(params string[] lines) { System.Media.SystemSounds.Asterisk.Play(); string msg = string.Join(Environment.NewLine + Environment.NewLine, lines); return MessageBox.Show(msg, "Alert", MessageBoxButtons.OK, MessageBoxIcon.Information); } internal static DialogResult Prompt(MessageBoxButtons btn, params string[] lines) { System.Media.SystemSounds.Question.Play(); string msg = string.Join(Environment.NewLine + Environment.NewLine, lines); return MessageBox.Show(msg, "Prompt", btn, MessageBoxIcon.Asterisk); } internal static int GetIndex(ComboBox cb) { return (int)(cb?.SelectedValue ?? 0); } public static void PanelScroll(object sender, ScrollEventArgs e) { if (!(sender is Panel p) || e.NewValue < 0) return; switch (e.ScrollOrientation) { case ScrollOrientation.HorizontalScroll: p.HorizontalScroll.Value = e.NewValue; break; case ScrollOrientation.VerticalScroll: p.VerticalScroll.Value = e.NewValue; break; } } public static void RemoveDropCB(object sender, KeyEventArgs e) => ((ComboBox)sender).DroppedDown = false; public static IEnumerable GetAllControlsOfType(Control control, Type type) { var controls = control.Controls.Cast().ToList(); return controls.SelectMany(ctrl => GetAllControlsOfType(ctrl, type)) .Concat(controls) .Where(c => c.GetType() == type); } #endregion #if CLICKONCE public static bool IsClickonceDeployed => System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed; #else public static bool IsClickonceDeployed => false; #endif /// /// Opens a dialog to open a , file, or any other supported file. /// /// Misc extensions of files supported by the SAV. /// Output result path /// Result of whether or not a file is to be loaded from the output path. public static bool OpenSAVPKMDialog(IEnumerable Extensions, out string path) { string supported = string.Join(";", Extensions.Select(s => $"*.{s}").Concat(new[] { "*.pkm" })); OpenFileDialog ofd = new OpenFileDialog { Filter = "All Files|*.*" + $"|Supported Files|main;*.sav;*.dat;*.gci;*.bin;{supported};*.bak" + "|3DS Main Files|main" + "|Save Files|*.sav;*.dat;*.gci" + "|Decrypted PKM File|" + supported + "|Binary File|*.bin" + "|Backup File|*.bak" }; // Detect main string cgse = ""; string pathCache = CyberGadgetUtil.GetCacheFolder(); if (Directory.Exists(pathCache)) cgse = Path.Combine(pathCache); if (!PathUtilWindows.DetectSaveFile(out path, cgse) && !string.IsNullOrEmpty(path)) { Error(path); // `path` contains the error message path = null; } if (path != null) ofd.FileName = path; if (ofd.ShowDialog() != DialogResult.OK) return false; path = ofd.FileName; return true; } /// /// Opens a dialog to save a file. /// /// file to be saved. /// Result of whether or not the file was saved. public static bool SavePKMDialog(PKM pk) { string pkx = pk.Extension; bool allowEncrypted = pk.Format > 3 || pk is PK3; SaveFileDialog sfd = new SaveFileDialog { Filter = $"Decrypted PKM File|*.{pkx}" + (allowEncrypted ? $"|Encrypted PKM File|*.e{pkx.Substring(1)}" : "") + "|Binary File|*.bin" + "|All Files|*.*", DefaultExt = pkx, FileName = Util.CleanFileName(pk.FileName) }; if (sfd.ShowDialog() != DialogResult.OK) return false; SavePKM(pk, sfd.FileName, pkx); return true; } private static void SavePKM(PKM pk, string path, string pkx) { SaveBackup(path); string ext = Path.GetExtension(path); var data = $".{pkx}" == ext ? pk.DecryptedBoxData : pk.EncryptedPartyData; File.WriteAllBytes(path, data); } private static void SaveBackup(string path) { if (!File.Exists(path)) return; // File already exists, save a .bak string bakpath = $"{path}.bak"; if (!File.Exists(bakpath)) File.Move(path, bakpath); } /// /// Opens a dialog to save a file. /// /// to be saved. /// Box the player will be greeted with when accessing the PC ingame. /// Result of whether or not the file was saved. public static bool SaveSAVDialog(SaveFile SAV, int CurrentBox = 0) { // Chunk Error Checking string err = SAV.MiscSaveChecks(); if (err.Length > 0 && Prompt(MessageBoxButtons.YesNo, err, "Continue saving?") != DialogResult.Yes) return false; SaveFileDialog main = new SaveFileDialog { Filter = SAV.Filter, FileName = SAV.FileName, RestoreDirectory = true }; if (Directory.Exists(SAV.FilePath)) main.InitialDirectory = SAV.FilePath; // Export if (main.ShowDialog() != DialogResult.OK) return false; if (SAV.HasBox) SAV.CurrentBox = CurrentBox; bool dsv = Path.GetExtension(main.FileName)?.ToLower() == ".dsv"; bool gci = Path.GetExtension(main.FileName)?.ToLower() == ".gci"; try { File.WriteAllBytes(main.FileName, SAV.Write(dsv, gci)); SAV.Edited = false; Alert("SAV exported to:", main.FileName); } catch (Exception x) { if (x is UnauthorizedAccessException || x is FileNotFoundException || x is IOException) Error("Unable to save." + Environment.NewLine + x.Message, "If destination is a removable disk (SD card), please ensure the write protection switch is not set."); else throw; } return true; } /// /// Opens a dialog to save a file. /// /// to be saved. /// Result of whether or not the file was saved. public static bool SaveMGDialog(MysteryGift gift) { SaveFileDialog output = new SaveFileDialog { Filter = GetMysterGiftFilter(gift.Format), FileName = Util.CleanFileName(gift.FileName) }; if (output.ShowDialog() != DialogResult.OK) return false; string path = output.FileName; if (File.Exists(path)) { // File already exists, save a .bak string bakpath = $"{path}.bak"; if (!File.Exists(bakpath)) File.Move(path, bakpath); } File.WriteAllBytes(path, gift.Data); return true; } public static string GetMysterGiftFilter(int Format) { switch (Format) { case 4: return "Gen4 Mystery Gift|*.pgt;*.pcd;*.wc4|All Files|*.*"; case 5: return "Gen5 Mystery Gift|*.pgf|All Files|*.*"; case 6: return "Gen6 Mystery Gift|*.wc6;*.wc6full|All Files|*.*"; case 7: return "Gen7 Mystery Gift|*.wc7;*.wc7full|All Files|*.*"; default: return string.Empty; } } } }