From a0dca6a5be5ebe75f916db6070b10e809d3acc10 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 17 Jul 2019 13:47:43 -0700 Subject: [PATCH] Improve save & restore of top line Whenever the display list gets regenerated, we need to restore the code list view scroll position to the previous location in the file. This gets tricky when multiple lines are appearing or disappearing. We were saving the file offset of the line, but that works poorly when there's a multi-line comment associated with that offset, because we end up scrolling to the top of the comment whenever any part of the comment is at the top of the screen. We now track the file offset and the number of lines we were from the top of that offset's content. This works well unless we remove a lot of lines. If the adjusted line index would put us into a different file offset, we punt and just scroll to the top of the item. Also, fix a crasher in Edit Note. Also, fix behavior when the list shrinks while a line near the end of the file is selected. Also, change a few instances of "Color.FromArgb(0,0,0,0)" to use a common constant. --- CommonWPF/Helper.cs | 2 + SourceGenWPF/DisplayList.cs | 8 +++- SourceGenWPF/DisplayListSelection.cs | 8 +++- SourceGenWPF/LineListGen.cs | 58 +++++++++++++++++++++---- SourceGenWPF/MainController.cs | 5 +-- SourceGenWPF/MultiLineComment.cs | 2 +- SourceGenWPF/NavStack.cs | 2 +- SourceGenWPF/Tests/ProgressMessage.cs | 2 +- SourceGenWPF/WpfGui/DiscardChanges.xaml | 4 +- SourceGenWPF/WpfGui/EditNote.xaml.cs | 9 +++- SourceGenWPF/WpfGui/MainWindow.xaml.cs | 4 +- 11 files changed, 80 insertions(+), 24 deletions(-) diff --git a/CommonWPF/Helper.cs b/CommonWPF/Helper.cs index 1649380..a278b70 100644 --- a/CommonWPF/Helper.cs +++ b/CommonWPF/Helper.cs @@ -24,6 +24,8 @@ namespace CommonWPF { /// Miscellaneous helper functions. /// public static class Helper { + public static Color ZeroColor = Color.FromArgb(0, 0, 0, 0); + /// /// Measures the size of a string when rendered with the specified parameters. Uses /// the current culture, left-to-right flow, and 1 pixel per DIP. diff --git a/SourceGenWPF/DisplayList.cs b/SourceGenWPF/DisplayList.cs index bb64329..57877fa 100644 --- a/SourceGenWPF/DisplayList.cs +++ b/SourceGenWPF/DisplayList.cs @@ -303,7 +303,7 @@ namespace SourceGenWPF { " new=" + newCount + " (mList.Count=" + mList.Count + ")"); Debug.Assert(startIndex >= 0 && startIndex < mList.Count); - Debug.Assert(oldCount > 0 && startIndex + oldCount < mList.Count); + Debug.Assert(oldCount > 0 && startIndex + oldCount <= mList.Count); Debug.Assert(newCount >= 0); // Remove the old elements to clear them. @@ -358,7 +358,7 @@ namespace SourceGenWPF { public int ListIndex { get; set; } = -1; - private static Color NoColor = Color.FromArgb(0, 0, 0, 0); + private static Color NoColor = CommonWPF.Helper.ZeroColor; // Private constructor -- create instances with factory methods. @@ -448,6 +448,10 @@ namespace SourceGenWPF { newParts.HasAddrLabelHighlight = false; return newParts; } + + public override string ToString() { + return "[Parts: index=" + ListIndex + " off=" + Offset + "]"; + } } } } diff --git a/SourceGenWPF/DisplayListSelection.cs b/SourceGenWPF/DisplayListSelection.cs index 1bbe8de..faf4778 100644 --- a/SourceGenWPF/DisplayListSelection.cs +++ b/SourceGenWPF/DisplayListSelection.cs @@ -111,8 +111,12 @@ namespace SourceGenWPF { this[parts.ListIndex] = true; } foreach (DisplayList.FormattedParts parts in e.RemovedItems) { - Debug.Assert(parts.ListIndex >= 0 && parts.ListIndex < mSelection.Length); - this[parts.ListIndex] = false; + Debug.Assert(parts.ListIndex >= 0); + if (parts.ListIndex < mSelection.Length) { + this[parts.ListIndex] = false; + } else { + Debug.WriteLine("Attempted to remove selected item off end of list: " + parts); + } } } diff --git a/SourceGenWPF/LineListGen.cs b/SourceGenWPF/LineListGen.cs index accf113..79f62a1 100644 --- a/SourceGenWPF/LineListGen.cs +++ b/SourceGenWPF/LineListGen.cs @@ -75,6 +75,8 @@ namespace SourceGenWPF { // Extremely-negative offset value ensures it's at the very top. public const int HEADER_COMMENT_OFFSET = int.MinValue + 1; + // These need to be bit flags so we can record which parts associated with a + // given offset are selected. [FlagsAttribute] public enum Type { Unclassified = 0, @@ -131,7 +133,7 @@ namespace SourceGenWPF { public FormattedParts Parts { get; set; } /// - /// Background color, used for notes. + /// Background color, used for Notes. /// public Color BackgroundColor { get; set; } @@ -213,11 +215,28 @@ namespace SourceGenWPF { private List mSelectionTags = new List(); + private class Top { + // File offset of line. + public int FileOffset { get; private set; } + // Number of lines between the first line at the specified offset and the + // target line. + public int LineDelta { get; private set; } + + public Top(int fileOffset, int lineDelta) { + FileOffset = fileOffset; + LineDelta = lineDelta; + Debug.WriteLine("New Top: " + this); + } + public override string ToString() { + return "[Top: off=+" + FileOffset.ToString("x6") + " delta=" + LineDelta + "]"; + } + } + /// /// This is a place to save the file offset associated with the ListView's /// TopItem, so we can position the list appropriately. /// - private int mTopOffset; + private Top mTopPosition; // Use Generate(). private SavedSelection() { } @@ -230,12 +249,18 @@ namespace SourceGenWPF { /// /// Display list, with list of Lines. /// Bit vector specifying which lines are selected. + /// Index of line that appears at the top of the list + /// control. /// New SavedSelection object. public static SavedSelection Generate(LineListGen dl, DisplayListSelection sel, - int topOffset) { + int topIndex) { SavedSelection savedSel = new SavedSelection(); //Debug.Assert(topOffset >= 0); - savedSel.mTopOffset = topOffset; + + int topOffset = dl[topIndex].FileOffset; + int firstIndex = dl.FindLineIndexByOffset(topOffset); + Debug.Assert(topIndex >= firstIndex); + savedSel.mTopPosition = new Top(topOffset, topIndex - firstIndex); List lineList = dl.mLineList; Debug.Assert(lineList.Count == sel.Length); @@ -313,7 +338,7 @@ namespace SourceGenWPF { // If a line encompassing this offset was at the top of the ListView // control before, use this line's index as the top. - if (topIndex < 0 && lineList[lineIndex].Contains(mTopOffset)) { + if (topIndex < 0 && lineList[lineIndex].Contains(mTopPosition.FileOffset)) { topIndex = lineIndex; } @@ -336,14 +361,29 @@ namespace SourceGenWPF { // Continue search for topIndex, if necessary. while (topIndex < 0 && lineIndex < lineList.Count) { - if (lineList[lineIndex].Contains(mTopOffset)) { + if (lineList[lineIndex].Contains(mTopPosition.FileOffset)) { topIndex = lineIndex; break; } lineIndex++; } - Debug.WriteLine("TopOffset +" + mTopOffset.ToString("x6") + - " --> index " + topIndex); + Debug.WriteLine("TopOffset " + mTopPosition + " --> index " + topIndex); + + // Adjust position within an element. This is necessary so we don't jump to + // the top of multi-line long comments or notes whenever any part of that + // comment or note is at the top of the list. + if (topIndex >= 0 && mTopPosition.LineDelta > 0) { + int adjIndex = topIndex + mTopPosition.LineDelta; + if (adjIndex >= lineList.Count || + lineList[adjIndex].FileOffset != mTopPosition.FileOffset) { + Debug.WriteLine("Can't adjust top position"); + // can't adjust; maybe they deleted several lines from comment + } else { + topIndex = adjIndex; + Debug.WriteLine("Top index adjusted to " + adjIndex); + } + } + if (topIndex < 0) { // This can happen if you delete the header comment while scrolled // to the top of the list. @@ -775,7 +815,7 @@ namespace SourceGenWPF { out MultiLineComment headerComment)) { List formatted = headerComment.FormatText(formatter, string.Empty); StringListToLines(formatted, Line.HEADER_COMMENT_OFFSET, Line.Type.LongComment, - Color.FromArgb(0, 0, 0, 0), tmpLines); + CommonWPF.Helper.ZeroColor, tmpLines); } // Format symbols. diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs index 6e9d6aa..2f4fbb7 100644 --- a/SourceGenWPF/MainController.cs +++ b/SourceGenWPF/MainController.cs @@ -697,8 +697,7 @@ namespace SourceGenWPF { mReanalysisTimer.StartTask("Save selection"); int topItemIndex = mMainWin.CodeListView_GetTopIndex(); LineListGen.SavedSelection savedSel = LineListGen.SavedSelection.Generate( - CodeLineList, mMainWin.CodeDisplayList.SelectedIndices, - CodeLineList[topItemIndex].FileOffset); + CodeLineList, mMainWin.CodeDisplayList.SelectedIndices, topItemIndex); //savedSel.DebugDump(); // Clear this so we don't try to fiddle with it later. @@ -1692,7 +1691,7 @@ namespace SourceGenWPF { MultiLineComment oldNote; if (!mProject.Notes.TryGetValue(offset, out oldNote)) { - oldNote = new MultiLineComment(string.Empty); + oldNote = null; } EditNote dlg = new EditNote(mMainWin, oldNote); dlg.ShowDialog(); diff --git a/SourceGenWPF/MultiLineComment.cs b/SourceGenWPF/MultiLineComment.cs index aab6868..8032b11 100644 --- a/SourceGenWPF/MultiLineComment.cs +++ b/SourceGenWPF/MultiLineComment.cs @@ -62,7 +62,7 @@ namespace SourceGenWPF { Text = text; BoxMode = false; MaxWidth = 80; - BackgroundColor = Color.FromArgb(0, 0, 0, 0); + BackgroundColor = CommonWPF.Helper.ZeroColor; } /// diff --git a/SourceGenWPF/NavStack.cs b/SourceGenWPF/NavStack.cs index 924ef0c..ef046be 100644 --- a/SourceGenWPF/NavStack.cs +++ b/SourceGenWPF/NavStack.cs @@ -70,7 +70,7 @@ namespace SourceGenWPF { return obj is Location && this == (Location)obj; } public override int GetHashCode() { - return Offset + (IsNote ? 65536 : 0); + return Offset + (IsNote ? (1<<24) : 0); } } diff --git a/SourceGenWPF/Tests/ProgressMessage.cs b/SourceGenWPF/Tests/ProgressMessage.cs index dd0a055..3ba75bd 100644 --- a/SourceGenWPF/Tests/ProgressMessage.cs +++ b/SourceGenWPF/Tests/ProgressMessage.cs @@ -26,7 +26,7 @@ namespace SourceGenWPF.Tests { public Color Color { get; private set; } public bool HasColor { get { return Color.A != 0; } } - public ProgressMessage(string msg) : this(msg, Color.FromArgb(0, 0, 0, 0)) { } + public ProgressMessage(string msg) : this(msg, CommonWPF.Helper.ZeroColor) { } public ProgressMessage(string msg, Color color) { Text = msg; diff --git a/SourceGenWPF/WpfGui/DiscardChanges.xaml b/SourceGenWPF/WpfGui/DiscardChanges.xaml index afe3bdd..441b604 100644 --- a/SourceGenWPF/WpfGui/DiscardChanges.xaml +++ b/SourceGenWPF/WpfGui/DiscardChanges.xaml @@ -32,9 +32,9 @@ limitations under the License.