From 26b60d88b6d848f3ba55ca046852e079be5fe3c6 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 5 Nov 2023 22:11:26 +1100 Subject: Add SCI_SELECTIONFROMPOINT for modifying multiple selections. --- call/ScintillaCall.cxx | 4 ++++ doc/ScintillaDoc.html | 6 ++++++ doc/ScintillaHistory.html | 3 +++ include/Scintilla.h | 1 + include/Scintilla.iface | 3 +++ include/ScintillaCall.h | 1 + include/ScintillaMessages.h | 1 + src/Editor.cxx | 25 +++++++++++++++++++++++++ src/Editor.h | 1 + src/Selection.cxx | 7 +++++++ src/Selection.h | 1 + test/simpleTests.py | 39 ++++++++++++++++++++++++++++++++++++--- 12 files changed, 89 insertions(+), 3 deletions(-) diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx index 6935eacbd..99e400e28 100644 --- a/call/ScintillaCall.cxx +++ b/call/ScintillaCall.cxx @@ -2887,6 +2887,10 @@ void ScintillaCall::AddSelection(Position caret, Position anchor) { Call(Message::AddSelection, caret, anchor); } +int ScintillaCall::SelectionFromPoint(int x, int y) { + return static_cast(Call(Message::SelectionFromPoint, x, y)); +} + void ScintillaCall::DropSelectionN(int selection) { Call(Message::DropSelectionN, selection); } diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html index dcabdd9ee..27a542e90 100644 --- a/doc/ScintillaDoc.html +++ b/doc/ScintillaDoc.html @@ -1199,6 +1199,7 @@ struct Sci_TextRangeFull { SCI_CLEARSELECTIONS
SCI_SETSELECTION(position caret, position anchor)
SCI_ADDSELECTION(position caret, position anchor)
+ SCI_SELECTIONFROMPOINT(int x, int y) → int
SCI_DROPSELECTIONN(int selection)
SCI_SETMAINSELECTION(int selection)
SCI_GETMAINSELECTION → int
@@ -1343,6 +1344,11 @@ struct Sci_TextRangeFull { Since there is always at least one selection, to set a list of selections, the first selection should be added with SCI_SETSELECTION and later selections added with SCI_ADDSELECTION

+

+ SCI_SELECTIONFROMPOINT(int x, int y) → int
+ Return the index of the selection at the point. If there is no selection at the point, return -1. + This can be used to drop a selection or make it the main selection.

+

SCI_DROPSELECTIONN(int selection)
If there are multiple selections, remove the indicated selection. diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html index b808bd4b8..2e26d8900 100644 --- a/doc/ScintillaHistory.html +++ b/doc/ScintillaHistory.html @@ -591,6 +591,9 @@ Released 18 November 2023.

  • + Add SCI_SELECTIONFROMPOINT for modifying multiple selections. +
  • +
  • Add SCI_SETMOVEEXTENDSSELECTION and SCI_CHANGESELECTIONMODE to simplify selection mode manipulation.
  • diff --git a/include/Scintilla.h b/include/Scintilla.h index 64238a5a3..8c555352c 100644 --- a/include/Scintilla.h +++ b/include/Scintilla.h @@ -1022,6 +1022,7 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP #define SCI_CLEARSELECTIONS 2571 #define SCI_SETSELECTION 2572 #define SCI_ADDSELECTION 2573 +#define SCI_SELECTIONFROMPOINT 2474 #define SCI_DROPSELECTIONN 2671 #define SCI_SETMAINSELECTION 2574 #define SCI_GETMAINSELECTION 2575 diff --git a/include/Scintilla.iface b/include/Scintilla.iface index c393d6179..918121596 100644 --- a/include/Scintilla.iface +++ b/include/Scintilla.iface @@ -2797,6 +2797,9 @@ fun void SetSelection=2572(position caret, position anchor) # Add a selection fun void AddSelection=2573(position caret, position anchor) +# Find the selection index for a point. -1 when not at a selection. +fun int SelectionFromPoint=2474(int x, int y) + # Drop one selection fun void DropSelectionN=2671(int selection,) diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h index 7f8088748..1a1a1e0f2 100644 --- a/include/ScintillaCall.h +++ b/include/ScintillaCall.h @@ -765,6 +765,7 @@ public: void ClearSelections(); void SetSelection(Position caret, Position anchor); void AddSelection(Position caret, Position anchor); + int SelectionFromPoint(int x, int y); void DropSelectionN(int selection); void SetMainSelection(int selection); int MainSelection(); diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h index eaf162ae5..4b9421242 100644 --- a/include/ScintillaMessages.h +++ b/include/ScintillaMessages.h @@ -678,6 +678,7 @@ enum class Message { ClearSelections = 2571, SetSelection = 2572, AddSelection = 2573, + SelectionFromPoint = 2474, DropSelectionN = 2671, SetMainSelection = 2574, GetMainSelection = 2575, diff --git a/src/Editor.cxx b/src/Editor.cxx index 627179876..527c9b47c 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -4511,6 +4511,28 @@ bool Editor::PointInSelection(Point pt) { return false; } +ptrdiff_t Editor::SelectionFromPoint(Point pt) { + // Prioritize checking inside non-empty selections since each character will be inside only 1 + const SelectionPosition posChar = SPositionFromLocation(pt, true, true); + for (size_t r = 0; r < sel.Count(); r++) { + if (sel.Range(r).ContainsCharacter(posChar)) { + return r; + } + } + + // Then check if near empty selections as may be near more than 1 + const SelectionPosition pos = SPositionFromLocation(pt, true, false); + for (size_t r = 0; r < sel.Count(); r++) { + const SelectionRange &range = sel.Range(r); + if ((range.Empty()) && (pos == range.caret)) { + return r; + } + } + + // No selection at point + return -1; +} + bool Editor::PointInSelMargin(Point pt) const { // Really means: "Point in a margin" if (vs.fixedColumnWidth > 0) { // There is a margin @@ -8676,6 +8698,9 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { Redraw(); break; + case Message::SelectionFromPoint: + return SelectionFromPoint(PointFromParameters(wParam, lParam)); + case Message::DropSelectionN: sel.DropSelection(wParam); ContainerNeedsUpdate(Update::Selection); diff --git a/src/Editor.h b/src/Editor.h index e7e198aec..19b0c730c 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -527,6 +527,7 @@ protected: // ScintillaBase subclass needs access to much of Editor /** PositionInSelection returns true if position in selection. */ bool PositionInSelection(Sci::Position pos); bool PointInSelection(Point pt); + ptrdiff_t SelectionFromPoint(Point pt); bool PointInSelMargin(Point pt) const; Window::Cursor GetMarginCursor(Point pt) const noexcept; void TrimAndSetSelection(Sci::Position currentPos_, Sci::Position anchor_); diff --git a/src/Selection.cxx b/src/Selection.cxx index fbdc474eb..53559e329 100644 --- a/src/Selection.cxx +++ b/src/Selection.cxx @@ -123,6 +123,13 @@ bool SelectionRange::ContainsCharacter(Sci::Position posCharacter) const noexcep return (posCharacter >= anchor.Position()) && (posCharacter < caret.Position()); } +bool SelectionRange::ContainsCharacter(SelectionPosition spCharacter) const noexcept { + if (anchor > caret) + return (spCharacter >= caret) && (spCharacter < anchor); + else + return (spCharacter >= anchor) && (spCharacter < caret); +} + SelectionSegment SelectionRange::Intersect(SelectionSegment check) const noexcept { const SelectionSegment inOrder(caret, anchor); if ((inOrder.start <= check.end) || (inOrder.end >= check.start)) { diff --git a/src/Selection.h b/src/Selection.h index 30e1e27ad..03ce5083c 100644 --- a/src/Selection.h +++ b/src/Selection.h @@ -129,6 +129,7 @@ struct SelectionRange { bool Contains(Sci::Position pos) const noexcept; bool Contains(SelectionPosition sp) const noexcept; bool ContainsCharacter(Sci::Position posCharacter) const noexcept; + bool ContainsCharacter(SelectionPosition spCharacter) const noexcept; SelectionSegment Intersect(SelectionSegment check) const noexcept; SelectionPosition Start() const noexcept { return (anchor < caret) ? anchor : caret; diff --git a/test/simpleTests.py b/test/simpleTests.py index fd1f7423a..4263ad036 100644 --- a/test/simpleTests.py +++ b/test/simpleTests.py @@ -809,11 +809,44 @@ class TestSimple(unittest.TestCase): self.assertEqual(self.ed.TargetEndVirtualSpace, 0) def testPointsAndPositions(self): - self.ed.AddText(1, b"x") + self.ed.SetContents(b"xyz") + + # Inter-character positions # Start of text - self.assertEqual(self.ed.PositionFromPoint(0,0), 0) + self.assertEqual(self.ed.PositionFromPoint(1,1), 0) # End of text - self.assertEqual(self.ed.PositionFromPoint(0,100), 1) + self.assertEqual(self.ed.PositionFromPoint(100, 1), 3) + self.assertEqual(self.ed.PositionFromPointClose(100, 1), -1) + + # Character positions + # Start of text + self.assertEqual(self.ed.CharPositionFromPoint(0,0), 0) + # End of text + self.assertEqual(self.ed.CharPositionFromPoint(100, 0), 3) + self.assertEqual(self.ed.CharPositionFromPointClose(100, 0), -1) + + def testSelectionFromPoint(self): + self.ed.SetContents(b"xxxxxx") + self.ed.SetSelection(3, 2) + self.ed.AddSelection(0, 1) + self.ed.AddSelection(5, 5) # Empty + xStart = self.ed.PointXFromPosition(0, 0) + xEnd = self.ed.PointXFromPosition(0, 5) + width = xEnd-xStart + charWidth = width // 5 + widthMid = charWidth // 2 + + posMid1 = xStart+widthMid + self.assertEqual(self.ed.SelectionFromPoint(posMid1, 1), 1) + posMid2 = xStart+charWidth+widthMid + self.assertEqual(self.ed.SelectionFromPoint(posMid2, 1), -1) + posMid3 = xStart+charWidth*2+widthMid + self.assertEqual(self.ed.SelectionFromPoint(posMid3, 1), 0) + # Empty selection at 5. Exact and then a few pixels either side + self.assertEqual(self.ed.SelectionFromPoint(xEnd, 1), 2) + self.assertEqual(self.ed.SelectionFromPoint(xEnd-2, 1), 2) + self.assertEqual(self.ed.SelectionFromPoint(xEnd+2, 1), 2) + self.assertEqual(self.ed.SelectionFromPoint(100, 0), -1) def testLinePositions(self): text = b"ab\ncd\nef" -- cgit v1.2.3