From 61ecb28d60c1be6b9eb61035f6f9b664826c0085 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sun, 16 Jun 2019 16:34:47 -0700 Subject: [PATCH] Add Edit Address Pretty simple dialog, but it took a while to figure out the best way to deal with input validation. Works from the various menus as well as double-clicking on .ORG and address column entries. Also, moved some stuff around to places that made more sense. --- SourceGenWPF/MainController.cs | 273 +++++++++++++++++--- SourceGenWPF/ProjWin/CodeListItemStyle.xaml | 10 +- SourceGenWPF/ProjWin/DataFileLoadIssue.xaml | 18 +- SourceGenWPF/ProjWin/DiscardChanges.xaml | 23 +- SourceGenWPF/ProjWin/EditAddress.xaml | 55 ++++ SourceGenWPF/ProjWin/EditAddress.xaml.cs | 121 +++++++++ SourceGenWPF/ProjWin/MainWindow.xaml | 6 +- SourceGenWPF/ProjWin/MainWindow.xaml.cs | 32 ++- SourceGenWPF/ProjWin/ProjectLoadIssue.xaml | 18 +- SourceGenWPF/Res/Strings.xaml | 18 +- SourceGenWPF/SourceGenWPF.csproj | 7 + 11 files changed, 518 insertions(+), 63 deletions(-) create mode 100644 SourceGenWPF/ProjWin/EditAddress.xaml create mode 100644 SourceGenWPF/ProjWin/EditAddress.xaml.cs diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs index da13261..a2a90f7 100644 --- a/SourceGenWPF/MainController.cs +++ b/SourceGenWPF/MainController.cs @@ -18,13 +18,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Text; +using System.Web.Script.Serialization; using System.Windows; using Asm65; using CommonUtil; using SourceGenWPF.ProjWin; -using System.Web.Script.Serialization; -using System.Text; namespace SourceGenWPF { /// @@ -57,7 +57,7 @@ namespace SourceGenWPF { /// /// Data backing the code list. /// - public LineListGen CodeListGen { get; private set; } + public LineListGen CodeLineList { get; private set; } #endregion Project state @@ -73,6 +73,11 @@ namespace SourceGenWPF { private List mRecentProjectPaths = new List(MAX_RECENT_PROJECTS); public const int MAX_RECENT_PROJECTS = 6; + /// + /// Analyzed selection state, updated whenever the selection changes. + /// + public SelectionState SelectionAnalysis { get; set; } + /// /// Activity log generated by the code and data analyzers. Displayed in window. /// @@ -139,6 +144,10 @@ namespace SourceGenWPF { /// private bool mUseMainAppDomainForPlugins = false; + private enum ColumnIndex { + Offset = 0, Address, Bytes, Flags, Attributes, Label, Opcode, Operand, Comment + } + #region Init and settings @@ -303,7 +312,7 @@ namespace SourceGenWPF { #endif // Finally, update the display list generator with all the fancy settings. - if (CodeListGen != null) { + if (CodeLineList != null) { // Regenerate the display list with the latest formatter config and // pseudo-op definition. (These are set as part of the refresh.) UndoableChange uc = @@ -398,7 +407,7 @@ namespace SourceGenWPF { dlg.ShowDialog(); } - CodeListGen = new LineListGen(mProject, mMainWin.CodeDisplayList, + CodeLineList = new LineListGen(mProject, mMainWin.CodeDisplayList, mOutputFormatter, mPseudoOpNames); RefreshProject(UndoableChange.ReanalysisScope.CodeAndData); @@ -481,9 +490,12 @@ namespace SourceGenWPF { mReanalysisTimer.StartTask("Save selection"); int topItemIndex = mMainWin.CodeListView_GetTopIndex(); LineListGen.SavedSelection savedSel = LineListGen.SavedSelection.Generate( - CodeListGen, mMainWin.CodeDisplayList.SelectedIndices, - CodeListGen[topItemIndex].FileOffset); + CodeLineList, mMainWin.CodeDisplayList.SelectedIndices, + CodeLineList[topItemIndex].FileOffset); //savedSel.DebugDump(); + + // Clear this so we don't try to fiddle with it later. + mTargetHighlightIndex = -1; mReanalysisTimer.EndTask("Save selection"); mReanalysisTimer.StartTask("Apply changes"); @@ -503,7 +515,7 @@ namespace SourceGenWPF { } mReanalysisTimer.EndTask(refreshTaskStr); - DisplayListSelection newSel = savedSel.Restore(CodeListGen, out topItemIndex); + DisplayListSelection newSel = savedSel.Restore(CodeLineList, out topItemIndex); //newSel.DebugDump(); // Restore the selection. The selection-changed event will cause updates to the @@ -556,8 +568,8 @@ namespace SourceGenWPF { Debug.WriteLine("CpuDef has changed, resetting formatter (now " + mProject.CpuDef + ")"); mOutputFormatter = new Formatter(mFormatterConfig); - CodeListGen.SetFormatter(mOutputFormatter); - CodeListGen.SetPseudoOpNames(mPseudoOpNames); + CodeLineList.SetFormatter(mOutputFormatter); + CodeLineList.SetPseudoOpNames(mPseudoOpNames); mOutputFormatterCpuDef = mProject.CpuDef; } @@ -605,7 +617,7 @@ namespace SourceGenWPF { IEnumerator iter = offsetSet.RangeListIterator; while (iter.MoveNext()) { RangeSet.Range range = iter.Current; - CodeListGen.GenerateRange(range.Low, range.High); + CodeLineList.GenerateRange(range.Low, range.High); } } @@ -633,7 +645,7 @@ namespace SourceGenWPF { } mReanalysisTimer.StartTask("Generate DisplayList"); - CodeListGen.GenerateAll(); + CodeLineList.GenerateAll(); mReanalysisTimer.EndTask("Generate DisplayList"); } @@ -955,6 +967,185 @@ namespace SourceGenWPF { public void HandleCodeListDoubleClick(int row, int col) { Debug.WriteLine("DCLICK: row=" + row + " col=" + col); + + // Clicking on some types of lines, such as ORG directives, results in + // specific behavior regardless of which column you click in. We're just + // checking the clicked-on line to decide what action to take. If it doesn't + // make sense to do for a multi-line selection, the action will have been + // disabled. + LineListGen.Line line = CodeLineList[row]; + switch (line.LineType) { + case LineListGen.Line.Type.EquDirective: + // Currently only does something for project symbols; platform symbols + // do nothing. +#if false + if (editProjectSymbolToolStripMenuItem.Enabled) { + EditProjectSymbol_Click(sender, e); + } +#endif + break; + case LineListGen.Line.Type.OrgDirective: + if (CanEditAddress()) { + EditAddress(); + } + break; + case LineListGen.Line.Type.RegWidthDirective: +#if false + if (overrideStatusFlagsToolStripMenuItem.Enabled) { + EditStatusFlags_Click(sender, e); + } +#endif + break; + case LineListGen.Line.Type.LongComment: +#if false + if (editLongCommentToolStripMenuItem.Enabled) { + EditLongComment_Click(sender, e); + } +#endif + break; + case LineListGen.Line.Type.Note: +#if false + if (editNoteToolStripMenuItem.Enabled) { + EditNote_Click(sender, e); + } +#endif + break; + + case LineListGen.Line.Type.Code: + case LineListGen.Line.Type.Data: + // For code and data, we have to break it down by column. + switch ((ColumnIndex)col) { + case ColumnIndex.Offset: + // does nothing + break; + case ColumnIndex.Address: + // edit address + if (CanEditAddress()) { + EditAddress(); + } + break; + case ColumnIndex.Bytes: +#if false + if (showHexDumpToolStripMenuItem.Enabled) { + ShowHexDump_Click(sender, e); + } +#endif + break; + case ColumnIndex.Flags: +#if false + if (overrideStatusFlagsToolStripMenuItem.Enabled) { + EditStatusFlags_Click(sender, e); + } +#endif + break; + case ColumnIndex.Attributes: + // does nothing + break; + case ColumnIndex.Label: +#if false + if (editLabelToolStripMenuItem.Enabled) { + EditLabel_Click(sender, e); + } +#endif + break; + case ColumnIndex.Opcode: + // File offset should always be valid, since we excluded the EQU + // statements and header comment earlier. + if (line.FileOffset >= 0) { + Anattrib attr = mProject.GetAnattrib(line.FileOffset); + FormatDescriptor dfd = attr.DataDescriptor; + + // Does this have an operand with an in-file target offset? + // (Resolve it as a numeric reference.) + if (attr.OperandOffset >= 0) { + // Yup, find the line for that offset and jump to it. + GoToOffset(attr.OperandOffset, false, true); + } else if (dfd != null && dfd.HasSymbol) { + // Operand has a symbol, do a symbol lookup. + int labelOffset = mProject.FindLabelOffsetByName( + dfd.SymbolRef.Label); + if (labelOffset >= 0) { + GoToOffset(labelOffset, false, true); + } + } else if (attr.IsDataStart || attr.IsInlineDataStart) { + // If it's an Address or Symbol, we can try to resolve + // the value. (Symbols should have been resolved by the + // previous clause, but Address entries would not have been.) + int operandOffset = DataAnalysis.GetDataOperandOffset( + mProject, line.FileOffset); + if (operandOffset >= 0) { + GoToOffset(operandOffset, false, true); + } + } + } + break; + case ColumnIndex.Operand: +#if false + if (editOperandToolStripMenuItem.Enabled) { + EditInstrDataOperand_Click(sender, e); + } +#endif + break; + case ColumnIndex.Comment: +#if false + if (editCommentToolStripMenuItem.Enabled) { + EditComment_Click(sender, e); + } +#endif + break; + + } + break; + + default: + Debug.WriteLine("Double-click: unhandled line type " + line.LineType); + break; + } + } + + public bool CanEditAddress() { + if (SelectionAnalysis.mNumItemsSelected != 1) { + return false; + } + EntityCounts counts = SelectionAnalysis.mEntityCounts; + // Line must be code, data, or an ORG directive. + return (counts.mDataLines > 0 || counts.mCodeLines > 0) || + (SelectionAnalysis.mLineType == LineListGen.Line.Type.OrgDirective); + } + + public void EditAddress() { + int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); + int offset = CodeLineList[selIndex].FileOffset; + Anattrib attr = mProject.GetAnattrib(offset); + + EditAddress dlg = new EditAddress(attr.Address, mProject.CpuDef.MaxAddressValue); + dlg.Owner = mMainWin; + bool? ok = dlg.ShowDialog(); + if (ok != true) { + return; + } + + if (offset == 0 && dlg.Address < 0) { + // Not allowed. The AddressMap will just put it back, which confuses + // the undo operation. + Debug.WriteLine("EditAddress: not allowed to remove address at offset +000000"); + } else if (attr.Address != dlg.Address) { + Debug.WriteLine("EditAddress: changing addr at offset +" + offset.ToString("x6") + + " to " + dlg.Address); + + AddressMap addrMap = mProject.AddrMap; + // Get the previous address map entry for this exact offset, if one + // exists. This may be different from the value used as the default + // (attr.Address), which is the address assigned to the offset, in + // the case where no previous mapping existed. + int prevAddress = addrMap.Get(offset); + UndoableChange uc = UndoableChange.CreateAddressChange(offset, + prevAddress, dlg.Address); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } else { + Debug.WriteLine("EditAddress: no change"); + } } /// @@ -966,7 +1157,7 @@ namespace SourceGenWPF { public void GoToOffset(int gotoOffset, bool jumpToNote, bool doPush) { int curSelIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); - int topLineIndex = CodeListGen.FindLineIndexByOffset(gotoOffset); + int topLineIndex = CodeLineList.FindLineIndexByOffset(gotoOffset); if (topLineIndex < 0) { Debug.Assert(false, "failed goto offset +" + gotoOffset.ToString("x6")); return; @@ -974,13 +1165,13 @@ namespace SourceGenWPF { int lastLineIndex; if (jumpToNote) { // Select all note lines, disregard the rest. - while (CodeListGen[topLineIndex].LineType != LineListGen.Line.Type.Note) { + while (CodeLineList[topLineIndex].LineType != LineListGen.Line.Type.Note) { topLineIndex++; - Debug.Assert(CodeListGen[topLineIndex].FileOffset == gotoOffset); + Debug.Assert(CodeLineList[topLineIndex].FileOffset == gotoOffset); } lastLineIndex = topLineIndex + 1; - while (lastLineIndex < CodeListGen.Count && - CodeListGen[lastLineIndex].LineType == LineListGen.Line.Type.Note) { + while (lastLineIndex < CodeLineList.Count && + CodeLineList[lastLineIndex].LineType == LineListGen.Line.Type.Note) { lastLineIndex++; } } else if (gotoOffset < 0) { @@ -988,8 +1179,8 @@ namespace SourceGenWPF { lastLineIndex = topLineIndex + 1; } else { // Advance to the code or data line. - while (CodeListGen[topLineIndex].LineType != LineListGen.Line.Type.Code && - CodeListGen[topLineIndex].LineType != LineListGen.Line.Type.Data) { + while (CodeLineList[topLineIndex].LineType != LineListGen.Line.Type.Code && + CodeLineList[topLineIndex].LineType != LineListGen.Line.Type.Data) { topLineIndex++; } lastLineIndex = topLineIndex + 1; @@ -1006,7 +1197,7 @@ namespace SourceGenWPF { if (doPush) { if (curSelIndex >= 0) { // Update the back stack and associated controls. - mNavStack.Push(CodeListGen[curSelIndex].FileOffset, gotoOffset); + mNavStack.Push(CodeLineList[curSelIndex].FileOffset, gotoOffset); #if false UpdateMenuItemsAndTitle(); #endif @@ -1052,8 +1243,8 @@ namespace SourceGenWPF { GoToOffset(fwdOff, false, false); } - public void SelectionChanged(out SelectionState newState) { - newState = UpdateSelectionState(); + public void SelectionChanged() { + SelectionAnalysis = UpdateSelectionState(); UpdateReferencesPanel(); UpdateInfoPanel(); @@ -1069,21 +1260,22 @@ namespace SourceGenWPF { // 0 if no lines are selected // 1 if a single *item* is selected (regardless of number of lines) // >1 if more than one item is selected (exact value not specified) - public int mNumSelected; + public int mNumItemsSelected; - // Single selection: the type of line selected. + // Single selection: the type of line selected. (Multi-sel: Unclassified) public LineListGen.Line.Type mLineType; - // Single selection: is line an instruction with an operand. + // Single selection: is line an instruction with an operand. (Multi-sel: False) public bool mIsInstructionWithOperand; - // Single selection: is line an EQU directive for a project symbol. + // Single selection: is line an EQU directive for a project symbol. (Multi-sel: False) public bool mIsProjectSymbolEqu; // Some totals. public EntityCounts mEntityCounts; public SelectionState() { + mLineType = LineListGen.Line.Type.Unclassified; mEntityCounts = new EntityCounts(); } } @@ -1106,9 +1298,9 @@ namespace SourceGenWPF { state.mEntityCounts = new EntityCounts(); } else if (IsSingleItemSelected()) { int firstIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); - state.mNumSelected = 1; + state.mNumItemsSelected = 1; state.mEntityCounts = GatherEntityCounts(firstIndex); - LineListGen.Line line = CodeListGen[firstIndex]; + LineListGen.Line line = CodeLineList[firstIndex]; state.mLineType = line.LineType; state.mIsInstructionWithOperand = (line.LineType == LineListGen.Line.Type.Code && @@ -1120,7 +1312,7 @@ namespace SourceGenWPF { state.mIsProjectSymbolEqu = (defSym.SymbolSource == Symbol.Source.Project); } } else { - state.mNumSelected = 2; + state.mNumItemsSelected = 2; state.mEntityCounts = GatherEntityCounts(-1); } @@ -1168,7 +1360,7 @@ namespace SourceGenWPF { // not selected, ignore continue; } - LineListGen.Line line = CodeListGen[i]; + LineListGen.Line line = CodeLineList[i]; switch (line.LineType) { case LineListGen.Line.Type.Code: codeLines++; @@ -1248,8 +1440,8 @@ namespace SourceGenWPF { } // Just check the first and last entries to see if they're the same. - LineListGen.Line firstItem = CodeListGen[firstIndex]; - LineListGen.Line lastItem = CodeListGen[lastIndex]; + LineListGen.Line firstItem = CodeLineList[firstIndex]; + LineListGen.Line lastItem = CodeLineList[lastIndex]; if (firstItem.FileOffset == lastItem.FileOffset && firstItem.LineType == lastItem.LineType) { return true; @@ -1279,7 +1471,7 @@ namespace SourceGenWPF { return -1; } int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); - LineListGen.Line line = CodeListGen[selIndex]; + LineListGen.Line line = CodeLineList[selIndex]; if (!line.IsCodeOrData) { return -1; } @@ -1288,13 +1480,13 @@ namespace SourceGenWPF { // Does this have an operand with an in-file target offset? Anattrib attr = mProject.GetAnattrib(line.FileOffset); if (attr.OperandOffset >= 0) { - return CodeListGen.FindCodeDataIndexByOffset(attr.OperandOffset); + return CodeLineList.FindCodeDataIndexByOffset(attr.OperandOffset); } else if (attr.IsDataStart || attr.IsInlineDataStart) { // If it's an Address or Symbol, we can try to resolve // the value. int operandOffset = DataAnalysis.GetDataOperandOffset(mProject, line.FileOffset); if (operandOffset >= 0) { - return CodeListGen.FindCodeDataIndexByOffset(operandOffset); + return CodeLineList.FindCodeDataIndexByOffset(operandOffset); } } return -1; @@ -1359,7 +1551,7 @@ namespace SourceGenWPF { if (firstByteOnly) { sel = new RangeSet(); foreach (int index in mMainWin.CodeDisplayList.SelectedIndices) { - int offset = CodeListGen[index].FileOffset; + int offset = CodeLineList[index].FileOffset; if (offset >= 0) { // Not interested in the header stuff for hinting. sel.Add(offset); @@ -1408,7 +1600,7 @@ namespace SourceGenWPF { RangeSet rs = new RangeSet(); foreach (int index in mMainWin.CodeDisplayList.SelectedIndices) { - int offset = CodeListGen[index].FileOffset; + int offset = CodeLineList[index].FileOffset; // Mark every byte of an instruction or multi-byte data item -- // everything that is represented by the line the user selected. @@ -1436,6 +1628,7 @@ namespace SourceGenWPF { #endregion Main window UI event handlers + #region References panel /// @@ -1453,7 +1646,7 @@ namespace SourceGenWPF { return; } int lineIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); - LineListGen.Line.Type type = CodeListGen[lineIndex].LineType; + LineListGen.Line.Type type = CodeLineList[lineIndex].LineType; if (type != LineListGen.Line.Type.Code && type != LineListGen.Line.Type.Data && type != LineListGen.Line.Type.EquDirective) { @@ -1462,7 +1655,7 @@ namespace SourceGenWPF { } // Find the appropriate xref set. - int offset = CodeListGen[lineIndex].FileOffset; + int offset = CodeLineList[lineIndex].FileOffset; XrefSet xrefs; if (offset < 0) { int index = LineListGen.DefSymIndexFromOffset(offset); @@ -1582,7 +1775,7 @@ namespace SourceGenWPF { return; } int lineIndex = mMainWin.CodeListView_GetFirstSelectedIndex(); - LineListGen.Line line = CodeListGen[lineIndex]; + LineListGen.Line line = CodeLineList[lineIndex]; StringBuilder sb = new StringBuilder(250); // TODO(someday): this should be made easier to localize diff --git a/SourceGenWPF/ProjWin/CodeListItemStyle.xaml b/SourceGenWPF/ProjWin/CodeListItemStyle.xaml index 1e6b32f..51e1988 100644 --- a/SourceGenWPF/ProjWin/CodeListItemStyle.xaml +++ b/SourceGenWPF/ProjWin/CodeListItemStyle.xaml @@ -1,5 +1,6 @@  + + + + + @@ -175,7 +181,7 @@ See also https://github.com/fadden/DisasmUiTest diff --git a/SourceGenWPF/ProjWin/DataFileLoadIssue.xaml b/SourceGenWPF/ProjWin/DataFileLoadIssue.xaml index 2e740e8..683277c 100644 --- a/SourceGenWPF/ProjWin/DataFileLoadIssue.xaml +++ b/SourceGenWPF/ProjWin/DataFileLoadIssue.xaml @@ -1,4 +1,20 @@ - + + + + + Title="Discard Changes?" + SizeToContent="WidthAndHeight" ResizeMode="NoResize" + ShowInTaskbar="False" WindowStartupLocation="CenterOwner"> diff --git a/SourceGenWPF/ProjWin/EditAddress.xaml b/SourceGenWPF/ProjWin/EditAddress.xaml new file mode 100644 index 0000000..f9f99a2 --- /dev/null +++ b/SourceGenWPF/ProjWin/EditAddress.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + +