using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using PKHeX.Core; using PKHeX.WinForms.Controls; using static PKHeX.Core.MessageStrings; namespace PKHeX.WinForms; public sealed partial class SAV_EventFlags2 : Form { private readonly EventWorkspace Editor; private readonly Dictionary WorkDict = []; private readonly Dictionary FlagDict = []; private bool editing; public SAV_EventFlags2(SAV2 sav) { InitializeComponent(); WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); var editor = Editor = new EventWorkspace(sav, sav.Version); AllowDrop = true; DragEnter += Main_DragEnter; DragDrop += Main_DragDrop; tabControl1.SelectedTab = GB_Research; editing = true; CB_Stats.Items.Clear(); for (int i = 0; i < editor.Values.Length; i++) CB_Stats.Items.Add(i.ToString()); TC_Flags.SuspendLayout(); TC_Const.SuspendLayout(); AddFlagList(editor.Labels, editor.Flags); AddConstList(editor.Labels, editor.Values); if (Application.IsDarkModeEnabled) { WinFormsTranslator.ReformatDark(TC_Flags); WinFormsTranslator.ReformatDark(TC_Const); } TC_Flags.ResumeLayout(); TC_Const.ResumeLayout(); Text = $"{Text} ({sav.Version})"; if (CB_Stats.Items.Count > 0) { CB_Stats.SelectedIndex = 0; } else { L_Stats.Visible = CB_Stats.Visible = MT_Stat.Visible = false; tabControl1.TabPages.Remove(GB_Constants); } NUD_Flag.Maximum = editor.Flags.Length - 1; NUD_Flag.Value = 0; c_CustomFlag.Checked = editor.Flags[0]; editing = false; } protected override void OnShown(EventArgs e) { base.OnShown(e); tabControl1.SelectedIndex = 0; } private void B_Cancel_Click(object sender, EventArgs e) { Close(); } private void B_Save_Click(object sender, EventArgs e) { Editor.Save(); Close(); } private void AddFlagList(EventLabelCollection list, bool[] values) { FlagDict.Clear(); TC_Flags.TabPages.Clear(); var labels = list.Flag; if (labels.Count == 0) { TC_Flags.Visible = false; var research = new Label { Text = MsgResearchRequired, Name = "TLP_Flags_Research", ForeColor = WinFormsUtil.ColorWarn, AutoSize = true, Location = new Point(20, 20) }; GB_Flags.Controls.Add(research); return; } foreach (var group in labels.GroupBy(z => z.Type).OrderBy(z => (int)z.Key)) { var tab = new TabPage { Name = $"Tab_F{group.Key}", Text = group.Key.ToString(), }; var grid = CreateFlagGrid(); var search = CreateSearchBox(text => ApplyGridFilter(grid, text)); var host = CreateSearchHost(search, grid); tab.Controls.Add(host); TC_Flags.TabPages.Add(tab); var cFlag = new DataGridViewCheckBoxColumn { DisplayIndex = 0, Width = 20, SortMode = DataGridViewColumnSortMode.NotSortable, }; var cLabel = new DataGridViewTextBoxColumn { DisplayIndex = 1, AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill, ReadOnly = true, SortMode = DataGridViewColumnSortMode.NotSortable, }; cFlag.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; cLabel.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft; grid.Columns.Add(cFlag); grid.Columns.Add(cLabel); var grouped = group.ToList(); grid.Rows.Add(grouped.Count); for (var i = 0; i < grouped.Count; i++) FlagDict[grouped[i].Index] = (grid, i); for (var i = 0; i < grouped.Count; i++) { var (name, index, _) = grouped[i]; var cells = grid.Rows[i].Cells; cells[0].Value = values[index]; cells[1].Value = name; } grid.CellValueChanged += (_, e) => { if (e.ColumnIndex != 0 || e.RowIndex == -1) return; var chk = (bool)grid.Rows[e.RowIndex].Cells[0].Value!; var index = grouped[e.RowIndex].Index; values[index] = chk; if (NUD_Flag.Value == index) c_CustomFlag.Checked = chk; }; grid.CellMouseUp += (_, e) => { if (e.RowIndex == -1) return; if (e.ColumnIndex == 0) { grid.EndEdit(); return; } if (e.ColumnIndex != 1) return; var chk = (bool)grid.Rows[e.RowIndex].Cells[0].Value!; grid.Rows[e.RowIndex].Cells[0].Value = !chk; var index = grouped[e.RowIndex].Index; values[index] = !chk; if (NUD_Flag.Value == index) c_CustomFlag.Checked = !chk; }; } } private static void ApplyGridFilter(DataGridView grid, string text) { var query = text.Trim(); var hasQuery = query.Length != 0; foreach (DataGridViewRow row in grid.Rows) { if (row.Cells.Count < 2) continue; if (!hasQuery) { row.Visible = true; continue; } var value = row.Cells[1].Value?.ToString() ?? string.Empty; row.Visible = value.Contains(query, StringComparison.CurrentCultureIgnoreCase); } } private static DoubleBufferedDataGridView CreateFlagGrid() => new() { AllowUserToAddRows = false, AllowUserToDeleteRows = false, AllowUserToResizeColumns = false, AllowUserToResizeRows = false, BackgroundColor = SystemColors.ControlLightLight, BorderStyle = BorderStyle.None, ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single, ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize, ColumnHeadersVisible = false, Dock = DockStyle.Fill, EditMode = DataGridViewEditMode.EditOnEnter, Margin = Padding.Empty, MultiSelect = false, RowHeadersVisible = false, SelectionMode = DataGridViewSelectionMode.CellSelect, ShowEditingIcon = false, }; private void AddConstList(EventLabelCollection list, ReadOnlySpan values) { WorkDict.Clear(); TC_Const.TabPages.Clear(); var labels = list.Work; if (labels.Count == 0) { TC_Const.Visible = false; GB_Constants.Controls.Add(new Label { Text = MsgResearchRequired, Name = "TLP_Const_Research", ForeColor = WinFormsUtil.ColorWarn, AutoSize = true, Location = new Point(20, 20) }); return; } foreach (var group in labels.GroupBy(z => z.Type).OrderBy(z => (int)z.Key)) { var tab = new TabPage { Name = $"Tab_W{group.Key}", Text = WinFormsTranslator.TranslateEnum(group.Key, Main.CurrentLanguage), }; var panel = CreateConstPanel(); TC_Const.TabPages.Add(tab); var grouped = group.ToList(); var rows = new List<(string Search, Label Label, ComboBox Combo, NumericUpDown Numeric)>(grouped.Count); for (var i = 0; i < grouped.Count; i++) { var entry = grouped[i]; var lbl = new Label { Text = entry.Name, Margin = Padding.Empty, AutoSize = true }; var mtb = new NumericUpDown { Maximum = byte.MaxValue, Minimum = byte.MinValue, Margin = Padding.Empty, Width = 50, }; var map = entry.PredefinedValues.Select(z => new ComboItem(z.Name, z.Value)).ToList(); var cb = new ComboBox { Margin = Padding.Empty, Width = 150, DropDownStyle = ComboBoxStyle.DropDownList, BindingContext = BindingContext, DropDownWidth = Width + 100, }; if (Application.IsDarkModeEnabled) WinFormsTranslator.ReformatDark(cb); cb.InitializeBinding(); cb.DataSource = map; lbl.Click += (_, _) => mtb.Value = 0; bool updating = false; mtb.ValueChanged += ChangeConstValue; void ChangeConstValue(object? sender, EventArgs e) { if (updating) return; updating = true; var value = (byte)mtb.Value; var (_, valueID) = map.Find(z => z.Value == value) ?? map[0]; if (WinFormsUtil.GetIndex(cb) != valueID) cb.SelectedValue = valueID; Editor.Values[entry.Index] = value; if (CB_Stats.SelectedIndex == entry.Index) MT_Stat.Text = ((int)mtb.Value).ToString(); updating = false; } cb.SelectedValueChanged += (_, _) => { if (editing || updating) return; var value = WinFormsUtil.GetIndex(cb); mtb.Value = value == NamedEventConst.CustomMagicValue ? 0 : value; }; mtb.Value = values[entry.Index]; if (mtb.Value == 0) ChangeConstValue(this, EventArgs.Empty); rows.Add((entry.Name, lbl, cb, mtb)); WorkDict.Add(entry.Index, mtb); } ApplyConstFilter(panel, rows, string.Empty); var search = CreateSearchBox(text => ApplyConstFilter(panel, rows, text)); var host = CreateSearchHost(search, panel); tab.Controls.Add(host); } } private static void ApplyConstFilter(TableLayoutPanel panel, IReadOnlyList<(string Search, Label Label, ComboBox Combo, NumericUpDown Numeric)> rows, string text) { var query = text.Trim(); var hasQuery = query.Length != 0; panel.SuspendLayout(); panel.Controls.Clear(); var rowIndex = 0; for (var i = 0; i < rows.Count; i++) { var row = rows[i]; if (hasQuery && !row.Search.Contains(query, StringComparison.CurrentCultureIgnoreCase)) continue; panel.Controls.Add(row.Label, 0, rowIndex); panel.Controls.Add(row.Combo, 1, rowIndex); panel.Controls.Add(row.Numeric, 2, rowIndex); rowIndex++; } panel.ResumeLayout(); } private static TextBox CreateSearchBox(Action applyFilter) { var box = new TextBox { PlaceholderText = "Search...", Dock = DockStyle.Top, Margin = Padding.Empty, }; if (Application.IsDarkModeEnabled) WinFormsTranslator.ReformatDark(box); var timer = new Timer { Interval = 150 }; timer.Tick += (_, _) => { timer.Stop(); applyFilter(box.Text); }; box.TextChanged += (_, _) => { timer.Stop(); timer.Start(); }; box.Disposed += (_, _) => timer.Dispose(); return box; } private static Panel CreateSearchHost(TextBox search, Control content) { var host = new Panel { Dock = DockStyle.Fill, Margin = Padding.Empty, }; content.Dock = DockStyle.Fill; host.Controls.Add(content); host.Controls.Add(search); return host; } private static TableLayoutPanel CreateConstPanel() { var panel = new TableLayoutPanel { AutoScroll = true, ColumnCount = 3, Dock = DockStyle.Fill, Margin = new Padding(4, 3, 4, 3), Name = "TLP_Const", RowCount = 1, }; panel.ColumnStyles.Add(new ColumnStyle()); panel.ColumnStyles.Add(new ColumnStyle()); panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 485F)); panel.RowStyles.Add(new RowStyle()); panel.RowStyles.Add(new RowStyle()); panel.Scroll += WinFormsUtil.PanelScroll; return panel; } private void ChangeCustomBool(object sender, EventArgs e) { if (editing) return; editing = true; var index = (int)NUD_Flag.Value; Editor.Flags[index] = c_CustomFlag.Checked; if (FlagDict.TryGetValue(index, out var result)) result.Grid.Rows[result.RowIndex].Cells[0].Value = c_CustomFlag.Checked; editing = false; } private void ChangeCustomFlag(object sender, EventArgs e) { int flag = (int)NUD_Flag.Value; c_CustomFlag.Checked = Editor.Flags[flag]; } private void ChangeCustomFlag(object sender, KeyEventArgs e) => ChangeCustomFlag(sender, (EventArgs)e); private void ChangeConstantIndex(object sender, EventArgs e) { var constants = Editor.Values; var index = CB_Stats.SelectedIndex; MT_Stat.Text = constants[index].ToString(); } private void ChangeCustomConst(object sender, EventArgs e) { if (editing) return; editing = true; var index = CB_Stats.SelectedIndex; var parse = byte.TryParse(MT_Stat.Text, out var value) ? value : (byte)0; Editor.Values[index] = parse; if (WorkDict.TryGetValue(index, out var mtb)) mtb.Value = parse; editing = false; } private void ChangeSAV(object sender, EventArgs e) => ChangeSAV(); private void ChangeSAV() { if (TB_NewSAV.Text.Length != 0 && TB_OldSAV.Text.Length != 0) DiffSaves(); } private void OpenSAV(object sender, EventArgs e) { using var ofd = new OpenFileDialog(); ofd.Title = MsgFileLoadSaveSelectGame; if (ofd.ShowDialog() == DialogResult.OK) LoadSAV(sender, ofd.FileName); } private void LoadSAV(object sender, string path) { var dest = sender == B_LoadOld ? TB_OldSAV : TB_NewSAV; dest.Text = path; } private void DiffSaves() { var diff = new EventBlockDiff(TB_OldSAV.Text, TB_NewSAV.Text); if (diff.Message != EventWorkDiffCompatibility.Valid) { WinFormsUtil.Alert(diff.Message.GetMessage()); return; } TB_IsSet.Text = string.Join(", ", diff.SetFlags.Select(z => $"{z:0000}")); TB_UnSet.Text = string.Join(", ", diff.ClearedFlags.Select(z => $"{z:0000}")); if (diff.WorkDiff.Count == 0) { WinFormsUtil.Alert("No Event Constant diff found."); return; } var promptCopy = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Copy Event Constant diff to clipboard?"); if (promptCopy == DialogResult.Yes) WinFormsUtil.SetClipboardText(string.Join(Environment.NewLine, diff.WorkDiff)); } private static void Main_DragEnter(object? sender, DragEventArgs? e) { if (e?.Data is null) return; if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy; } private void Main_DragDrop(object? sender, DragEventArgs? e) { if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] { Length: not 0 } files) return; foreach (var file in files) { var result = this.SelectNewOld(file, B_LoadNew.Text, B_LoadOld.Text); if (result == DualDiffSelection.New) TB_NewSAV.Text = file; else if (result == DualDiffSelection.Old) TB_OldSAV.Text = file; else if (ModifierKeys == Keys.Escape) return; } ChangeSAV(); } }