Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Drag & Drop in Edit Controls


  • Please log in to reply
22 replies to this topic
Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
In edit controls the following script allows moving pieces of texts around by dragging the selection. Ctrl-drag with the left mouse button duplicates the selection to the position of releasing the button. (If it is released inside the selection, the selection is just lifted.) There is a limitation: the EM_CHARFROMPOS message works only in edit controls with at most 64K characters, so you get unexpected results in large ones, like in Notepad at a large text file.
#SingleInstance force

#NoEnv

SetBatchLines -1



VarSetCapacity(A1,4)

VarSetCapacity(A2,4)



^LButton::

$LButton::

   MouseGetPos mX, mY, Win,Ctrl                                ; Info about pointer location

   ControlGetPos cX, cY,,,%Ctrl%, ahk_id %Win%                 ; Top-left corner pos of Ctrl

   SendMessage 0xD7,,% (mY-cY)<<16|(mX-cX),%Ctrl%,ahk_id %Win% ; CharFromPos

   c := ErrorLevel & 0xFFFF                                    ; char index < 64K

   SendMessage 0xB0, &A1, &A2, %Ctrl%, ahk_id %Win%            ; GetSel

   c1 := *(&A1) + (*(&A1+1)<<8)                                ; Selection start

   c2 := *(&A2) + (*(&A2+1)<<8)                                ; Selection end

   If (Ctrl <> "Edit1" or c < c1 or c2 <= c)                   ; Clicked not in edit1 selection

      SendInput {LButton Down}

Return



^LButton Up::

$LButton Up::

   If (Ctrl <> "Edit1" or c < c1 or c2 <= c) {                 ; Clicked not in edit1 selection

      SendInput {LButton Up}

      Return

   }

   MouseGetPos mX, mY                                          ; Pointer location

   SendMessage 0xD7,,% (mY-cY)<<16|(mX-cX),%Ctrl%,ahk_id %Win% ; CharFromPos at button up

   c := ErrorLevel & 0xFFFF                                    ; char index < 64K

   If (c1 <= c and c < c2) {                                   ; Pointer still in selection

      SendInput {LButton}

      Return

   }

   ControlGet T, Selected,,%Ctrl%,ahk_id %Win%

   StringReplace T, T, `r,,All

   If Asc(A_ThisHotKey) = Asc("^")                             ; Ctrl-Drag: duplicates

      SendInput {LButton}{RAW}%T%

   Else If (c < c1)                                            ; Deleting selection keeps caret

      SendInput {DEL}{LButton}{RAW}%T%

   Else {                                                      ; Deleting selection moves caret

      SendInput {LButton}{RAW}%T%                              ; Insert text

      Sleep 50                                                 ; Time to update

      SendMessage 0xB1, c1, c2, %Ctrl%, ahk_id %Win%           ; SetSel re-select

      SendMessage 0xC2,  1, "", %Ctrl%, ahk_id %Win%           ; ReplaceSel = deletes

      SendMessage 0xB1, c+c1-c2, c+c1-c2, %Ctrl%, ahk_id %Win% ; SetSel set insertion point

   }

Return
There are some other limitations, too, like single character selections are hard to move.

Rajat
  • Members
  • 1904 posts
  • Last active: Jul 17 2015 07:45 AM
  • Joined: 28 Mar 2004
cool enhancement to simple notepad! playing around with Edit controls lately, eh?! :p

MIA

CleanNews.in : Bite sized latest news headlines from India with zero bloat


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
AGU asked for it, and I figured it would be useful, in general. At a hotkey I have in my main script a GUI to pop up with an edit control. There I can edit short scripts, and pressing the hotkey again executes them. Two problems annoyed me the most: 8 char tab stops and the lack of drag'n drop. Now I am :D

AGU
  • Guests
  • Last active:
  • Joined: --
YES, AGU asked for it ;) and AGU says a big THANK YOU now. :mrgreen:
Awesome.

btw. do you see any chance to add a little cursor that moves along the line when dragging highlighted text? Don't know how to describe it properly.
You can see what I mean, when you highlight some text within the edit control where you write you posting within phpbb. When dragging the highlighted text a cursor/caret moves along the line. It helps to see where/at what position the dragged text would paste.
______________________________
Cheers
AGU

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I think you don't like the mouse pointer (vertical line) during dragging, because it does not exactly match to the insertion point. A pointer aligned with the text can be done, just give me a couple of days. Now I am trying to increase the size of the edit control above 64K characters. It looks like 64K lines (instead of chars) can be handled with some tricks. Beyond that I don't see any simple solutions.

freakkk
  • Members
  • 182 posts
  • Last active: Dec 16 2014 06:23 PM
  • Joined: 29 Jul 2005
Laszlo-- that is absolutely awesome!! I am still studying & learning from it :D Thanks for sharing!

do you see any chance to add a little cursor that moves along the line when dragging highlighted text?

You could always try changing the mouse cursor while dragging.. but I'm not sure if it would work as smoothly as you want it too..

Edit: Whoops- Laszlo just said that!

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Here is the next experimental version. It works in any edit control if its name starts with Edit.

The index (position) of the character closest to the mouse pointer is calculated using the index of the line, so edit controls can be handled with up to 64K lines, each at most 64K characters long. (64K lines > 1300 pages of text with 50 lines per page.)

Messages are used to insert/delete selections. They are faster than sending keystrokes.
#SingleInstance force
#NoEnv
Process Priority,,High
SetBatchLines -1

#UseHook
VarSetCapacity(A1,4)
VarSetCapacity(A2,4)

^LButton::
LButton::
   MouseGetPos mX, mY, Win,Ctrl                                ; Info about pointer location
   ControlGetPos cX, cY,,,%Ctrl%, ahk_id %Win%                 ; Top-left corner pos of Ctrl
   c := Char(mX-cX, mY-cY, Ctrl, Win)                          ; Char#
   SendMessage 0xB0, &A1, &A2, %Ctrl%, ahk_id %Win%            ; GetSel
   c1 := *(&A1)+(*(&A1+1)<<8)+(*(&A1+2)<<16)+(*(&A1+3)<<24)    ; Selection start
   c2 := *(&A2)+(*(&A2+1)<<8)+(*(&A2+2)<<16)+(*(&A2+3)<<24)    ; Selection end
   If (InStr(Ctrl,"Edit") <> 1 or c < c1 or c2 <= c)           ; Clicked not in Edit, or in selection
      SendInput {LButton Down}
Return

~^LButton Up::
~LButton Up::
   If (InStr(Ctrl,"Edit") <> 1 or c < c1 or c2 <= c)           ; Clicked not in Edit, or in selection
      Return
   MouseGetPos mX, mY                                          ; Pointer location
   c := Char(mX-cX, mY-cY, Ctrl, Win)                          ; Char#
   If (c1 <= c and c < c2) {                                   ; Pointer still in selection
      Click                                                    ; Unselect
      Return
   }
   ControlGet T, Selected,,%Ctrl%,ahk_id %Win%                 ; Save selected text
   If A_ThisHotKey = ~^LButton Up                              ; Ctrl-Drag: duplicates
   {
      SendMessage 0xB1, c,  c, %Ctrl%, ahk_id %Win%            ; SetSel set insertion point
      SendMessage 0xC2, 1, &T, %Ctrl%, ahk_id %Win%            ; ReplaceSel = insert T
      Return
   }
   SendMessage 0xC2, 0, "", %Ctrl%, ahk_id %Win%               ; ReplaceSel = delete  selection
   IfLess c,%c1%, SendMessage 0xB1, c, c, %Ctrl%, ahk_id %Win% ; SetSel mouse -> insertion point
   Else SendMessage 0xB1,c+c1-c2,c+c1-c2, %Ctrl% ,ahk_id %Win% ; SetSel insertion point moved left
   SendMessage 0xC2, 1, &T, %Ctrl%, ahk_id %Win%               ; ReplaceSel = insert T
Return

Char(x,y,Ctrl,Win) {                                           ; Char# of pixel (x,y)
   SendMessage 0xD7,,% y<<16|x,%Ctrl%,ahk_id %Win%             ; CharFromPos
   c = %ErrorLevel%                                            ; line# || char# (16,16-bit)
   SendMessage 0xBB,ErrorLevel>>16,,%Ctrl%,ahk_id %Win%        ; LineIndex
   Return Errorlevel + ((c-Errorlevel) & 0xFFFF)               ; Char# > 16-bit
}
To Do:
1. Auto-scroll at the edges when dragging
2. Showing actual insertion point with special pointer
3. Noch mehr Wünsche?

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Here is the necessary information on the messages used:
EM_CHARFROMPOS 0xD7

   wParam: not used.

   lParam: The low/high-order word contains the horizontal/vertical coordinate.

           Relative to the upper-left corner of the control's client area.

   -->   : The low-order word = zero-based index of the character nearest the specified point.

             Relative to the beginning of the control.

             If the point is beyond the last visible character in a line: the line delimiter.

           The high-order word = zero-based index of the line that contains the character.

             If the specified point is beyond the last character: the last line in the control.



EM_GETFIRSTVISIBLELINE 0xCE

   wParam: 0

   lParam: 0

   -->   : Zero-based index of the uppermost visible line in a multiline edit control



EM_GETSEL      0xB0

   wParam: Pointer to buffer that receives the starting position of the selection or NULL.

   lParam: Pointer to buffer <-- the first nonselected char after the end of selection or NULL.

   -->   : Low-order word = Zero-based value with the starting position of the selection

           High-order word = position of first character after the last selected character.

           If either of these values exceeds 65,535, the return value is -1.

   (Better to use values returned in wParam and lParam because they are full 32-bit values.)



EM_LINEINDEX   0xBB

   wParam: Zero-based line number. -1: the current line number (with the caret).

   lParam: not used.

   -->   : Index of the first char of line specified in the wParam parameter

           -1 if line number is greater than the number of lines.



EM_LINESCROLL  0xB6

   wParam: The number of characters to scroll horizontally.

   lParam: The number of lines to scroll vertically.

   -->   : If the message is sent to a multiline edit control, TRUE, else FALSE



EM_POSFROMCHAR 0xD6

   wParam: The zero-based index of the character.

   lParam: not used.

   -->   : CLIENT area coordinates of the character. Low/High-order word: horizontal/vertical coordinate

           Negative if the specified character is not displayed in the edit control's client area.

           If the character is line delimiter, the coordinates indicate a point beyond the last visible char

           If the specified index > index of the last char in the control: -1.



EM_REPLACESEL  0xC2

   wParam: Specifies whether the replacement operation can be undone (TRUE) or not (FALSE).

   lParam: Pointer to a null-terminated string containing the replacement text.

   (If there is no selection, the replacement text is inserted at the current location of the caret.)



EM_SETSEL      0xB1

   wParam: Specifies the Starting character position of the selection.

   lParam: Specifies the Ending character position of the selection. (start <,> end OK)

   (Displays flashing caret at the End position regardless of the relative values of Start and End)



EM_SETTABSTOPS 0xCB

   wParam: Number of tab stops contained in the array.

           0: lParam parameter is ignored, default tab stops are set at every 32 dialog template units.

           1: tab stops are set at every n dialog template units, n is the distance pointed to by lParam.

          >1: lParam is a pointer to an array of tab stops.

   lParam: Pointer to array of unsigned integers specifying the tab stops, in dialog template units.

           If wParam = 1: lParam is a pointer to unsigned integer = distance between all tab stops.

   -->   : TRUE/FALSE If all the tabs are set, or not.
I collected them from MSDN.

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004
To extend the limit of an Edit/Richedit control you could try sending an EM_EXLIMITTEXT Message (EM_EXLIMITTEXT = 1077) to the control using SendMessage/PostMessage. I don't remember what the maximum extended limit for an edit control is offhand (but there is a maximum upper limit AFAIK)... IIRC a RichEdit control can be extended to approx. 4 Gig...

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

The EM_EXLIMITTEXT message sets an upper limit to the amount of text the user can type or paste into a rich edit control.

We deal with normal edit controls, and the issue is not how much text we can enter, but how to find the index of the character nearest to the mouse pointer. The EM_CHARFROMPOS message returns only a 16-bit index, which makes the first script fail at longer than 64K char texts. The trick that works is to get the line index of this character, which is also 16 bits. Another message finds the 32-bit index of the first character of this line, which allows the computation of longer than 16-bit character indices.

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004

We deal with normal edit controls, and the issue is not how much text we can enter, but how to find the index of the character nearest to the mouse pointer.

Normal Edit controls are limited to 64K by default. I haven't tested the limit on an AutoHotkey Edit control but if Chris didn't extend it then I would be surprised if it accepted any input over 64K (pasted or manually entered). The EM_EXLIMITTEXT message can also be used to extend a normal Edit control. At least I'm pretty sure I've used it on an Edit control (maybe I'm mistaken). If it doesn't work then you could try a EM_LIMITTEXT Message instead :) .

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The size of edit controls might be OS specific. In my XP SP2, a GUI edit control does not have the 64K char limit. Nor does Notepad. EM_EXLIMITTEXT might be necessary for older Windows versions, I don't know. Have you tried?

corrupt
  • Members
  • 2558 posts
  • Last active: Nov 01 2014 03:23 PM
  • Joined: 29 Dec 2004

The size of edit controls might be OS specific. In my XP SP2, a GUI edit control does not have the 64K char limit. Nor does Notepad. EM_EXLIMITTEXT might be necessary for older Windows versions, I don't know. Have you tried?

I haven't in a while. I had previously run into this issue when putting together an Editor using another language. IIRC the default limit may only apply to Win9.x systems (not sure about Win2K).

P.S. - Added a link for EM_LIMITTEXT above for Edit controls

Edit: The link gives the limits for edit controls in different versions of Windows near the bottom

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The next experimental version is below. New features:

- If you drag beyond the edges of the edit control, it scrolls vertically or horizontally (even diagonally)
- During drag the selection flashes
- The actual insertion point in text is shown with a flashing caret (vertical line). The mouse pointer can be at a different place, the caret is the nearest valid position in the text.
- At insertion the top line in the control remains the same. Otherwise, if there was some scrolling, the line with the insertion point gets to the top or the bottom.
- The text dragged to a new position gets selected. This way you see the result better, and if you missed the right insertion point, you can just continue dragging.
#SingleInstance force
#NoEnv
Process Priority,,High
SetBatchLines -1

#UseHook                                                    ; No need for $ in hotkeys
VarSetCapacity(A1,4)
VarSetCapacity(A2,4)                                        ; Room for 32-bit Integers

^LButton::
LButton::
   MouseGetPos mX, mY, Win,Ctrl                             ; Info about pointer location
   ControlGetPos cX, cY,cW,cH, %Ctrl%, ahk_id %Win%         ; Top-left corner, Width, Height of Ctrl
   c := Char#(mX-cX, mY-cY, Ctrl, Win)                      ; Char# nearest to mouse
   SendMessage 0xB0, &A1, &A2, %Ctrl%, ahk_id %Win%         ; GetSel
   c1 := *(&A1)+(*(&A1+1)<<8)+(*(&A1+2)<<16)+(*(&A1+3)<<24) ; Selection start
   c2 := *(&A2)+(*(&A2+1)<<8)+(*(&A2+2)<<16)+(*(&A2+3)<<24) ; Selection end
   If (InStr(Ctrl,"Edit") <> 1 or c < c1 or c2 <= c)        ; Clicked not in Edit, or in selection
      SendInput {LButton Down}
   Else
      SetTimer Cursor, 100                                  ; Show insertion point
Return

~^LButton Up::
~LButton Up::
   SetTimer Cursor, OFF                                     ; Stop flashing selection, insertion point
   If (InStr(Ctrl,"Edit") <> 1 or c < c1 or c2 <= c)        ; Clicked not in Edit, or in selection
      Return
   MouseGetPos mX, mY                                       ; Pointer location
   c := Char#(mX-cX, mY-cY, Ctrl, Win)                      ; Char# nearest to mouse
   If (c1 <= c and c < c2)                                  ; Pointer still in selection
      Return
   SendMessage 0xCE,  0,  0,  %Ctrl%, ahk_id %Win%          ; GetFirstVisibleLine Top of Ctrl
   tp = %ErrorLevel%
   SendMessage 0xB1, c1, c2,  %Ctrl%, ahk_id %Win%          ; SetSel re-select
   ControlGet T, Selected, ,  %Ctrl%, ahk_id %Win%          ; Save selected text
   If A_ThisHotKey = ~LButton Up                            ; Drag moves, Ctrl-Drag duplicates
      SendMessage 0xC2, 0,"", %Ctrl%, ahk_id %Win%          ; ReplaceSel = delete selection
   If (A_ThisHotKey = "~LButton Up" and c1 < c)             ; Deleted text moves insertion point
      c := c+c1-c2
   SendMessage 0xB1, c,   c,  %Ctrl%, ahk_id %Win%          ; SetSel mouse -> insertion point
   SendMessage 0xC2, 1,  &T,  %Ctrl%, ahk_id %Win%          ; ReplaceSel = insert T
   SendMessage 0xB1,c,c+c2-c1,%Ctrl%, ahk_id %Win%          ; SetSel select moved text
   SendMessage 0xCE,  0,  0,  %Ctrl%, ahk_id %Win%          ; GetFirstVisibleLine New-top
   SendMessage 0xB6,0,tp-ErrorLevel,%Ctrl%,ahk_id %Win%     ; Scroll to restore line positions
Return

Cursor:                                                     ; Flashing selection + caret
   MouseGetPos mX, mY                                       ; Pointer location
   v := (mY-cY > cH) - (mY-cY < 0)                          ; Up/Down: -1/1
   h := (mX-cX > cW) - (mX-cX < 0)                          ; Left/Right: -1/1
   SendMessage 0xB6, h, v, %Ctrl%, ahk_id %Win%             ; Scroll
   curs := !curs                                            ; Flash
   If curs
      SendMessage 0xB1,c1,c2, %Ctrl%, ahk_id %Win%          ; SetSel original selection
   Else {
      d := Char#(mX-cX, mY-cY, Ctrl, Win)                   ; Char# nearest to mouse
      SendMessage 0xB1, d, d, %Ctrl%, ahk_id %Win%          ; SetSel mouse -> current insert point
   }
Return

Char#(x,y,Ctrl,Win) {                                       ; Char# of pixel (x,y)
   SendMessage 0xD7,,% y<<16|x,%Ctrl%,ahk_id %Win%          ; CharFromPos
   c = %ErrorLevel%                                         ; line# || char# (16,16-bit)
   SendMessage 0xBB,ErrorLevel>>16,,%Ctrl%,ahk_id %Win%     ; LineIndex
   Return Errorlevel + ((c-Errorlevel) & 0xFFFF)            ; Up to 32-bit Char#
}
If you like to change the mouse pointer or the caret while dragging, look here. I did not find it necessary, because the flashing selection informs about the dragging, but it is of personal preference.

This text dragging works fine, but being done with an interpreted script, it is not as smooth as dragging in some other controls. If someone turns the script into a dll, it might speed things up, but then we could just use another control supporting drag'n drop, with the same effort.

For testing open the script in Notepad, select everything (Ctrl-A) and Ctrl-Drag it to the end. If you repeat this a few times, you get long text to play with. You could even see what happens if you reach the limit of 64K lines.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The version below has stability improvements:

- Some applications (e.g. MS Word) crash, when EM messages are sent, which coincide with their own messages. Therefore, the hotkey returns immediately, if the active control is not named Edit*.
- AHK seems to have some problems with ~LButton Up hotkeys. As a workaround, the script below explicitly sends {LButton Up}, with the modifier keys (Ctrl).
- The flashing frequency of the selection is now 2 a second, while 10 scroll messages are sent a second, when needed.
#SingleInstance force
#NoEnv
Process Priority,,High
SetBatchLines -1
#UseHook                                                    ; Mouse Hook, no $ in hotkeys

VarSetCapacity(A1,4)
VarSetCapacity(A2,4)                                        ; Room for 32-bit Integers

^LButton::                                               ; Duplicate-drag
LButton::                                                ; Move-drag HOTKEYS
   MouseGetPos mX, mY, Win,Ctrl                             ; Info about pointer location
   If (InStr(Ctrl,"Edit") <> 1) {                           ; Clicked not in Edit control
      Send {Blind}{LButton Down}                            ; Standard mouse action, ^ is kept
      Return                                                ; No drag
   }
   ControlGetPos cX, cY,cW,cH, %Ctrl%, ahk_id %Win%         ; Top-left corner, Width, Height of Ctrl
   c := Char#(mX-cX, mY-cY, Ctrl, Win)                      ; Char# nearest to mouse
   SendMessage 0xB0, &A1, &A2, %Ctrl%, ahk_id %Win%         ; GetSel
   c1 := *(&A1)+(*(&A1+1)<<8)+(*(&A1+2)<<16)+(*(&A1+3)<<24) ; Selection start
   c2 := *(&A2)+(*(&A2+1)<<8)+(*(&A2+2)<<16)+(*(&A2+3)<<24) ; Selection end
   If (c < c1 or c2 <= c)                                   ; Clicked outside of selection
      Send {Blind}{LButton Down}                            ; Standard mouse action, ^ is kept
   Else
      SetTimer Cursor, 100                                  ; Flash selection, Show insertion point
Return

^LButton Up::                                            ; Selection moved at releasing button
LButton Up::
   SetTimer Cursor, OFF                                     ; Stop flashing selection, insertion point
   Send {Blind}{LButton Up}                                 ; Standard act, ^ kept (~LButton has bug)
   If (InStr(Ctrl,"Edit") <> 1 or c < c1 or c2 <= c)        ; Clicked not in Edit, or in selection
      Return
   MouseGetPos mX, mY                                       ; Pointer location
   c := Char#(mX-cX, mY-cY, Ctrl, Win)                      ; Char# nearest to mouse
   If (c1 <= c and c < c2)                                  ; Pointer still in selection
      Return
   SendMessage 0xCE,  0,  0,  %Ctrl%, ahk_id %Win%          ; GetFirstVisibleLine Top of Ctrl
   tp = %ErrorLevel%
   SendMessage 0xB1, c1, c2,  %Ctrl%, ahk_id %Win%          ; SetSel re-select
   ControlGet T, Selected, ,  %Ctrl%, ahk_id %Win%          ; Save selected text
   If A_ThisHotKey = LButton Up                             ; Drag moves, Ctrl-Drag duplicates
      SendMessage 0xC2, 0,"", %Ctrl%, ahk_id %Win%          ; ReplaceSel = delete selection
   If (A_ThisHotKey = "LButton Up" and c1 < c)              ; Deleted text moves insertion point
      c := c+c1-c2
   SendMessage 0xB1, c,   c,  %Ctrl%, ahk_id %Win%          ; SetSel mouse -> insertion point
   SendMessage 0xC2, 1,  &T,  %Ctrl%, ahk_id %Win%          ; ReplaceSel = insert T
   SendMessage 0xB1,c,c+c2-c1,%Ctrl%, ahk_id %Win%          ; SetSel select moved text
   SendMessage 0xCE,  0,  0,  %Ctrl%, ahk_id %Win%          ; GetFirstVisibleLine New-top
   SendMessage 0xB6,0,tp-ErrorLevel,%Ctrl%,ahk_id %Win%     ; Scroll to restore line positions
Return

Cursor:                                                     ; Flashing selection + caret
   MouseGetPos mX, mY                                       ; Pointer location
   v := (mY-cY > cH) - (mY-cY < 0)                          ; Up/Down: -1/1
   h := (mX-cX > cW) - (mX-cX < 0)                          ; Left/Right: -1/1
   SendMessage 0xB6, h, v, %Ctrl%, ahk_id %Win%             ; Scroll
   If A_TickCount & 256                                     ; Flash
      SendMessage 0xB1,c1,c2, %Ctrl%, ahk_id %Win%          ; SetSel original selection
   Else {
      d := Char#(mX-cX, mY-cY, Ctrl, Win)                   ; Char# nearest to mouse
      SendMessage 0xB1, d, d, %Ctrl%, ahk_id %Win%          ; SetSel mouse -> current insert point
   }
Return

Char#(x,y,Ctrl,Win) {                                       ; Char# nearest to pixel (x,y)
   SendMessage 0xD7,,% y<<16|x,%Ctrl%,ahk_id %Win%          ; CharFromPos
   c = %ErrorLevel%                                         ; line# || char# (16,16-bit)
   SendMessage 0xBB,ErrorLevel>>16,,%Ctrl%,ahk_id %Win%     ; LineIndex
   Return Errorlevel + ((c-Errorlevel) & 0xFFFF)            ; Up to 32-bit Char#
}