how to localize insertion point to x,y?

Good morning!

I would like to be able to find the X,Y location of the insertion point in a text area, the coordinates where you’re actually typing. I need to be able to popup some menu’s depending on what you’re doing and would like them to be positioned usefully under where you are actually typing.

At the moment I’m doing a serious amount of stupid by figuring out the line number of the text that the insertion point is in via LineNumAtCharPos( ScriptField.SelStart), then adding in the scroll position and multiplying by the textHeight of the font that I got from a separate picture graphics object set to the same font and style and size. Then walking forward through the line until the InsertionPosAtXY returns a number greater or equal to the selstart. Then add in the toolbar offsets and window margins and all the rest.

That is just a horrible hack, but it’s the best I could figure out and it sort of almost works most of the time. I feel like I should be able to do the opposite and have a “XYAtInsertionPoint” or some other way to get the relative local coordinates based on the selstart value.

This will only ever run on a Mac. I am not afraid of declares or plugins. I have gone through the NSTextFieldMBS and all it’s parent classes and I feel like the information is in there somewhere but I’m evidently not as versed on the lower level Apple cocoa philosophy as I wished. Any suggestions as to where to go to look or read up would be greatly appreciated. Thank you!

Well I kinda sorta worked something out that works better than the other method but requires the MBS plugins.

dim layoutManager as NSLayoutManagerMBS = TextField.NSTextViewMBS.layoutManager
dim horizOffset = layoutManager.locationForGlyphAtIndex( TextField.SelStart).x

dim workRange as new NSRange
dim workRect as NSRectMBS = layoutManager.lineFragmentRectForGlyphAtIndex( textField.selStart, workRange)
dim vertOffset = workRect.Y

then you have to adjust for the scroll position:
X scroll position seems to be in pixels, so you just subtract it

horizOffset = horizOffset - textField.ScrollPositionX)

vertical scrolling seems to be in lines not pixels so you have to subtract the width of the returned rect times it like

vertOffset = vertOffset - (textField.scrollPosition * workRect.height)

and then you have to add in any other control offsets and the window position. This almost works, but it seems there is a margin or a border property to the placement of the line fragment rects as the further down you scroll the further off it becomes. I shall dive back in and try to find that property of the layout manager or other part of the text rendering system. There is a lot of stuff in there…

You might be able to figure this out by drawing the text onto a Graphics object using the exact same settings as the TextArea.

How would I get the info from a graphics object to tell me what X,Y location glyph 908 was into the display? I know I can get something approaching the line height from it that way, but I can’t see how I can find the location of a specific character or find the insertion point?

There might be a better way but the methods you seek can be found in NSLayoutManager, the layoutManager property of NSTextView.

NSLayoutManager has several methods for positioning and measuring glyphs but I couldn’t find one that told both x and y in 1 call. Instead locationForGlyphAtIndex gets the X coordinate, which is relative to it’s ‘line fragment’. lineFragmentRectForGlyphAtIndex gets the rect of that line fragment for the Y.

Those X and Y don’t include scroll offset so I used documentVisibleRect to get scroll amount. Seems there’d be a more direct property but that’s what I found on StackOverflow.

TextArea1.Handle points to an NSScrollView and from there the declares walk…
NSScrollView->documentView->layoutManager->location and line fragment

Add a TextArea with lots of text and a Canvas with this code. Clicking or cursoring through the text will reposition the left edge of the canvas around the insertion line.

Sub process()        //Method
  const Cocoa = "Cocoa"
  declare function getDocView lib Cocoa selector "documentView" (id As integer) As integer
  declare function getLM lib Cocoa selector "layoutManager" (id As integer) As integer
  declare function loc4Glyph lib Cocoa selector "locationForGlyphAtIndex:" (id As integer, glyphIndex As UInt32) As NSPoint
  declare function lineFrag lib Cocoa selector "lineFragmentRectForGlyphAtIndex:effectiveRange:" (id As integer, glyphIndex As UInt32, effectiveGlyphRange As Ptr) As NSRect
  declare function getContentView lib Cocoa selector "contentView" (id As integer) As integer
  declare function docVisRect lib Cocoa selector "documentVisibleRect" (id As integer) As NSRect
  //glyph point and line rect
  dim dv As integer = getDocView(TextArea1.Handle)
  dim lm As integer = getLM(dv)
  dim p As NSPoint = loc4Glyph(lm, TextArea1.SelStart)
  dim r As NSRect = lineFrag(lm, TextArea1.SelStart, nil)
  dim cv As integer = getContentView(TextArea1.Handle)
  dim docRect As NSRect = docVisRect(cv)
  Canvas1.Left   = p.x + TextArea1.Left
  Canvas1.Top    = r.y + TextArea1.Top - docRect.y
  Canvas1.Height = r.h
  //Label1.Text = "p: " + Format(p.x, "-0.0") + ", " + Format(p.y, "-0.0") + ", r:" + Format(r.x, "-0.0") + ", " + Format(r.y, "-0.0") + ", "   + Format(r.w, "-0.0") + ", " + Format(r.h, "-0.0") + ", scroll: " + Format(docRect.y, "-0.0")
End Sub

Sub SelChange()   //TextArea1
End Sub

Sub Paint(g As Graphics, areas() As REALbasic.Rect)    //Canvas1
  g.ForeColor = &c00000080
  g.FillRect(0, 0, g.Width, g.Height)
End Sub

NSPoint          //Structures
  x As single
  y As single

  x As single
  y As single
  w As single
  h As single

??? A glaring hole in this code is that XY is invalid when the insertion cursor is at the very very end of text. There’s no glyph at this index so you’d have to get the right edge of the rect of the last glyph. Also I haven’t tested this with styled text or anything fancy, maybe there’s better more robust methods to use.

I will experiment! The documentVisibleRect seems to be the major missing element that I didn’t understand. thank you!