Sorry for the delay
At this point we have basic implementations for most useful things
There are still a number of items that we havent touched
BaseLineAtIndex, CharacterAtPoint, DiscardIncompleteText, KeyFallsThrough, SetIncompleteText, TextForRange, and even TextLength
Two others have minimal implementations - SelectedRange and IncompleteTextRange
And yet most things seem to work ?
So what are all these extras for ?
If you open the IDE and double click a word in the text then right click you should see the “Services” option at the bottom of the contextual menu on a Mac.
Some of these are involved in making it so those services can work.
But right now since we dont have a way to “select” anything we cant really make those work.
So thats what we’ll work on now.
On macOS its normal to implement click, double click and triple click.
A single click might simply move the cursor. A double click would select the word under the mouse, and a triple click might select the line under the cursor.
In order to track what kind of click it is we need to keep track of the X and Y position of a click and the duration since the last click.
So we’re going to add
- an enum for “clicktypes” - with 4 members “none”, “single”, “double”, “triple”
- a property to track the "click type (mClickType as ClickTypes)
- properties for lastClickX and Y (mLastClickX mLastClickY as double)
- a property for the last click time (mLastClickTime as double)
With that we can track clicks with some code like in the MouseDown event
// double and triple clicks are both TIME & SPACE
// if you click move a long way click this should not be a double or triple click
// triple click ?
If (mClickType = ClickTypes.Double) _
And (Ticks - mLastClickTime < DoubleClickInterval) _
And ( Abs(x - mLastClickX) < 5) _
And ( Abs(y - mLastClickY) < 5) Then
mClickType = ClickTypes.triple
dbglog currentmethodname + " triple click"
// double click ?
Elseif (mClickType = ClickTypes.Single) _
And (Ticks - mLastClickTime < DoubleClickInterval) _
And ( Abs(x - mLastClickX) < 5) _
And ( Abs(y - mLastClickY) < 5) Then
mClickType = ClickTypes.Double
dbglog currentmethodname + " double click"
Else
mClickType = ClickTypes.Single
dbglog currentmethodname + " single click"
End If
mLastClickX = x
mLastClickY = y
mLastClickTime = Ticks
Note that if you quadruple click that the click type will run through single, double, triple then back to single click. And it will continue to cycle through like that as you click rapidly.
And now that we are able to figure out what kind of click it is we can start making the code do selections when a person clicks
So lets start with the single click
First we’ll need to take the X, Y and turn that into a Line & column number, and move the cursor to that position.
So we’ll need a couple more helper methods - one that can take an X Y and return a line & column
And then one that can take a line and column and return the linear index position in our buffer
The method to convert from X Y to line & column will need a graphics surface to measure things using the SAME mechanism used during a paint event (otherwise things get out of what really quickly)
We already have this in one method so we should probably lift that into its own method we can just reuse. Lines 9 - 16 of RectForRange can be converted into a method named GetMeasuringPicture that returns a suitable picture set up the way we need.
So in RectForRange lines 8 - 11 now look like
// get the position as a line & column #
pos = PositionToLineAndColumn( range.Location )
Dim p As picture = GetMeasuringPicture
// get the x (left) edge and (y) baseline for the position
Dim position As REAlbasic.point = LineColumnToXY(p.Graphics, pos.X, pos.Y)
The method we just created, GetMeasuringPicture, will need a
return p
as its last line
And now in MouseDown lines 28 and on can be
mLastClickTime = Ticks
Dim p As Picture = GetMeasuringPicture
Dim line, col As Integer
XYToLineColumn(p.Graphics, x, y, line, col)
Select Case mClickType
Case ClickTypes.Single
mInsertionPosition = LineColumnToPosition( line, col )
End Select
XY to LineColumn can simply break things into lines and then determine which Y position encompasses the line of text. And then we can measure that line of text in increasing pieces to see where the X position fell and we’ll know the line & column number that is where the click point was.
Protected Sub XYToLineColumn(g as Graphics, x as double, y as double, byref lineNumber as integer, byref column as Integer)
Dim xPos As Double
Dim yPos As Double
// NOTE this is NOT EFFICIENT !!!!!!
// since we split things into lines in PAINT and again here
// if we really need lines we should figure out how to do this as few times as possible
Dim lines() As String = Split( ReplaceLineEndings(mTextBuffer, EndOfLine), EndOfLine )
Dim lineTopY As Double
lineTopY = 0
lineNumber = 0
// so a person may not click rih on the baseline ans we need to see if the
// click is anywhere from the top of the line to the bottom
While lineTopY < Y And lineTopY + g.TextHeight <= Y
lineNumber = lineNumber + 1
lineTopY = lineTopY + g.TextHeight
Wend
If lineNumber >= 0 And lineNumber <= lines.ubound Then
// ok wo what column does this X represent ?
column = 0
Dim lineX As Double = 0
Dim lineSeg As String = lines(lineNumber).Left(column)
While column <= lines(lineNumber).Len and g.StringWidth( lineseg ) < x
column = column + 1
lineSeg = lines(lineNumber).Left(column)
Wend
// column has been incremented once too many times
column = column - 1
End If
End Sub
And to know where to position the insertion point on a single click we need to convert that line and column in to a linear position in our internal buffer. And that too is reasonably straightforward and looks like
Protected Function LineColumnToPosition(line as integer, column as integer) as integer
// NOTE this is NOT EFFICIENT !!!!!!
// since we split things into lines in PAINT and again here
// if we really need lines we should figure out how to do this as few times as possible
Dim lines() As String = Split( ReplaceLineEndings(mTextBuffer, EndOfLine), EndOfLine )
Dim tmpPosition As Integer
// count up the lengths of whole lines
For i As Integer = 0 To line - 1
tmpPosition = tmpPosition + lines(i).Len
Next
// plus hte last line we add in just the columns since it may not be the wbole line
tmpPosition = tmpPosition + column
Return tmpPosition
End Function
And now when you single click the insertion position should move to where you clicked