; Copyright 2019 RISC OS Open Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > Wimp.s.CnPCaret

 [ CnP
; In: R0 = window handle
;     R1 = icon handle
;     R2 = X offset relative to window origin
;     R3 = Y offset relative to window origin
;     R4 = caret height/flags
;     R5 = string index
; [+  R6 = maximum selection index]
;We are given x,y coords relative to window work area origin

; remove existing caret or selection or whatever first, depending on how we're called

int_set_caret_position ROUT
        Push    "R0-R7,R11,R14"

        ; if we're called with R5=-1 then R4,R5 are calculated from R0-R3
        ; however, R4 is undefined on entry so could be anything.
        ; Internally we'd like to be able to use the call to put a ghost caret
        ; to a mouse click...
        ; we're doing the call by a SWI now, so need a way to get ghost caret calculations
        ; add R5=-2 for this purpose - calculate ghost caret details from x/y coords
        ; R4 *must* now be a valid flag set
        CMP     R5,#-1
        BNE     %FT00
        CMP     R1,#-1
        MOVNE   R4,#-1                 ; R4 now is -1 (ie no flags)
00
        CMP     R5,#-2
        MOVEQ   R5,#-1

        ; validate existing caret blocks
        LDR     handle,caretdata
        CMP     handle,#nullptr
        BLNE    checkhandle
        MOVVS   handle,#nullptr
        STRVS   handle,caretdata

        LDR     handle,ghostcaretdata
        CMP     handle,#nullptr
        BLNE    checkhandle
        MOVVS   handle,#nullptr
        STRVS   handle,ghostcaretdata

        LDR     R14,taskhandle          ; calling task
        Push    "R14"                   ; preserve for later

        CMP     R4,#-2
        MOVEQ   R4,#-1                  ; workaround bug in Filer 1.96-2.45 which calls with R4=-2 instead of -1

        LDR     R14,dragtype            ; is there currently an icon selection drag?
        TEQ     R14,#drag_icon_selection
        BNE     %FT00

        ; are we moving the main caret?  If not, doesn't matter
        CMP     R4,#-1                  ; R4=-1 implies main caret
        TSTNE   R4,#crf_ghostcaret :OR: crf_selection
        BNE     %FT00

        ; if so, then we need to see if we're relocating outside the icon
        ; if we are, then the drag should be cancelled

        LDR     R14,caretdata+caretwindow
        TEQ     R0,R14
        LDREQ   R14,caretdata+careticon
        TEQEQ   R1,R14
        BEQ     %FT00                   ; same window/icon so continue unchanged

        ; we need to stop the drag before proceeding
        Push    "R0,x0,y0,x1,y1"

        LDR     R0,caretdata+caretwindow
        Abs     handle,R0
        MOV     R14,#0
        STR     R14,dragtype
        MOV     R14,#bignum
        STR     R14,[handle,#w_seldata+wselxoverride]

        BL      clearpointerwindow      ; corrupts x0,y0,x1,y1

        BL      iconautoscroll_stop
        Pull    "R0,x0,y0,x1,y1"
00
        AcceptLoosePointer_NegOrZero R0,nullptr
        CMP     R0,R0,ASR #31
        BEQ     %FT07                   ; we know this one's not valid
        MOV     handle,R0
        BL      checkhandle             ; valid window handle?
        BVC     %FT01
        BVS     %FT08                   ; destination isn't valid, so just remove the caret
07
        ; if we've been called with r0=-1 then we may need to add some flags...
        LDR     R14,taskidentifier1     ; 'TASK'
        CMP     R2,R14                  ; is R2 'TASK'
        MOVNE   R4,#0                   ; if we're called with TASK then R4 has correct flags.  If not, we need
                                        ; to make sure we have some valid flags
        MOVEQ   R2,#0                   ; if we were called with R2='TASK' then
        STREQ   R2,[sp,#(2+1+8)*4]      ; ensure R2=0 on return - taskhandle stacked below r2
08
        CLRV                            ; clear V flag as we're not going to report this as an error
        SavePSR R6
        Push    "R6"
        STR     R0,[sp,#8]              ; preserve error message
        MOV     R0,#-1                  ; carry on as an invalid window handle

        BL      wscp_remove_current_caret ; remove the caret, queue redraw as necessary

        ; need to clear the appropriate data blocks
        MOV     R14,#nullptr
        CMP     R4,#-1
        TSTNE   R4,#crf_ghostcaret :OR: crf_selection   ; normal caret?
        STREQ   R14,caretdata
        BEQ     exitsetcaret_abort

        TST     R4,#crf_ghostcaret                      ; ghost caret?
        STRNE   R14,ghostcaretdata
        BNE     exitsetcaret_abort

        TST     R4,#crf_selection                       ; unshaded selection?
        BEQ     exitsetcaret_abort

        LDR     R11,selectionwindow
        CMP     R11,#nullptr
        BEQ     exitsetcaret_abort

        BL      clipboard_check_current_drag_op ; stop drag op if selection has been terminated

        Abs     R11,R11
        STR     R14,[R11,#w_seldata]                    ; update if handle is valid
        STR     R14,selectionwindow                     ; current selection window is now invalid too
        B       exitsetcaret_abort
01
        ; continues - r0 is a confirmed valid window handle

        ; is the window currently open and able to receive a focus?
        Push    "R1,R10"
        MOV     R1,#-1
        BL      setfocus
        Pull    "R1,R10"
        BVS     %BT08                                   ; no - abort and clear caret
02
        ; set up the hascaret etc. blocks for the current state of the icon before gaining caret
        LDR     R14,caretdata
        EORS    R14,R14,R0
        LDREQ   R14,caretdata+careticon
        EOREQ   R14,R14,R1
        STR     R14,hascaret

        LDR     R14,ghostcaretdata
        EORS    R14,R14,R0
        LDREQ   R14,ghostcaretdata+ghostcareticon
        EOREQ   R14,R14,R1
        STR     R14,hasghostcaret

        LDR     R14,[handle,#w_seldata]
        EOR     R14,R14,R1
        STR     R14,hasselection

        ; work out which block we're playing with - main or ghost caret
        CMP     R4,#-1
        TSTNE   R4,#crf_selection           ; are we doing a selection caret?  Different data blocks if we are.
        BNE     %FT40

        CMP     R4,#-1                      ; which caret block do we need?
        TSTNE   R4,#crf_ghostcaret
        ADREQL  R11,caretdata
        ADRNEL  R11,ghostcaretdata
        LDR     R14,[R11]
        STR     R14,oldcaretwindow

        BL      wscp_remove_current_caret
        ; was it OK?
        MOVVS   R14,#nullptr
        STRVS   R13,[R11]
        STRVS   R0,[sp,#4]                  ; taskhandle stacked first
        BVS     exitsetcaret                ; mark this caret as invalid and exit

        ; are we setting caret to an icon or just the work area?
        CMP     R1,#0
        BLT     wscp_workarea

        ; we're placing in an icon
        ; we either use string offset or the coords supplied

        ; is the icon handle valid?
        LDR     R14,[handle,#w_nicons]
        CMP     R1,R14                      ; icon handle out of range?
        BGT     exitsetcaret                ; abort if so

        LDR     R14,[handle,#w_taskhandle]
        Task    R14,,"SetCaret"             ; page in window owner task
        ; check string offsets to ensure valid (ie within string itself)
        CMP     R5,#0
        BLPL    wscp_make_offsets_valid

        ; sort checksum for the string if need to
        CMP     R4,#0
        ADRMIL  R14,caretx                  ; no flags in this situation, assume normal caret
        STRMI   R14,caretx_dest
        BMI     %FT18
        TST     R4,#crf_ghostcaret
        ADREQL  R14,caretx
        ADRNEL  R14,ghostcaretx
        STR     R14,caretx_dest             ; determine whose caretx gets set
        BEQ     %FT17

        Push    "R0,R11"
        LDR     R11,[handle,#w_icons]
        ADD     R11,R11,R1,LSL #i_shift     ; get icon address
        BL      writable_calc_checksum
        STR     R0,ghostcaretchecksum
        Pull    "R0,R11"
17
;        CMP     R4,#0
;        BMI     %FT18
        CMP     R5,#0
        BMI     %FT18

        Push    "R4"
        BL      setcaretcoords          ; recompute.  We may want to keep the height after
        BIC     R14,R4,#&ff000000
        BIC     R14,R14,#&ff0000
        Pull    "R4"
        BIC     R4,R4,#&ff
        BIC     R4,R4,#&ff00
        ORR     R4,R4,R14               ; merge height and flags
18
        CMP     R5,#0                   ; if R5<0 then R4,R5 <-- R0,R1,R2,R3
        BGE     %FT99

        Push    "R0,R1,R4"
        CMP     R2,#&10000
        MOVGT   R2,#&10000
        CMP     R3,#&10000
        MOVGT   R3,#&10000
        BL      findcaret               ; get R4,R5. We'll add the flags back in now.
        BIC     R14,R4,#&ff000000
        BIC     R14,R14,#&ff0000        ; remove flags (if any)
        Pull    "R0,R1,R4"
        CMP     R4,#-1
        MOVEQ   R4,R14
        BICNE   R4,R4,#&ff
        BICNE   R4,R4,#&ff00            ; remove old caret height
        ORRNE   R4,R4,R14               ; merge in new caret height, keeping flags
99
        CMP     R4,#-1                  ; if R4<0 then R2,R3,R4 <-- R0,R1,R5
        MOVEQ   R4,#0                   ; fix up flags for the call
        BLEQ    setcaretcoords

        TST     R4,#crf_ghostcaret
        ADREQL  R11,caretdata
        BEQ     %FT98

        ; override scrollx position?
        TST     R4,#crf_nocentre
        MOVEQ   R6,#bignum
        BEQ     %FT96

        ; if we were called with R5=-2, then new scrollx is in original R3, else it is in R2
        LDR     R6,[sp,#5*4]            ; get R5 from stack
        CMP     R6,#-2
        LDREQ   R6,[sp,#3*4]            ; get R3 from stack if R5 was -2
        LDRNE   R6,[sp,#2*4]            ; get R2 from stack otherwise
96
        ; if ghost caret is on top of a selection, hide the ghost caret
        LDR     R14,[handle,#w_seldata+wselicon]
        CMP     R14,#nullptr
        BEQ     %FT97                   ; no selection
        CMP     R14,R1
        BNE     %FT97                   ; different icon, so no need to check
        LDR     R14,[handle,#w_seldata+wsellowindex] ; low bound of selection
        CMP     R14,R5
        BGT     %FT97                   ; we're left of low bound
        LDR     R14,[handle,#w_seldata+wselhighindex] ; high bound
        CMP     R14,R5
        ORRGE   R4,R4,#crf_invisible    ; we're in selection zone, so hide caret
97
        ADRL    R11,ghostcaretdata
        STMIA   R11,{R0-R5}
        ; we've set/adjusted a ghost caret.
        ; was there a main caret in the same icon?
        ; if so, we'll need to recalculate its position
        BL      refreshcaret_ghost_main
        B       %FT20
98
        STMIA   R11,{R0-R5}             ; processed values
        ; ** no r6 for scrollx now

;        BL      check_shaded_selection_window

        ; focus on?
        LDR     R14,oldcaretwindow

        CMP     R0,R14
        BEQ     %FT20                   ; same window, no repeat messages
        BL      check_shaded_selection_window
        BL      send_gaincaret
        BL      focuson
        MOVVS   R14,#nullptr
        STRVS   R14,[R11]
        BVS     exitsetcaret
20
        ; redraw the target icon
        Push    "R0-R2"
        LDR     handle,[R11,#0]
        Abs     handle,handle           ; window handle of current caret
        LDR     R0,[R11,#4]             ; icon handle of current caret
        MOV     R1,#0
        MOV     R2,#0

        BL      int_set_icon_state
        Pull    "R0-R2"
        B       exitsetcaret

wscp_workarea
        ; setting a caret (ghost or main) to the window work area
        ; no need to calculate string offsets etc
        ; just place the caret and leave
        BL      upcaret
        BVS     %FT22

        ; upcaret corrupts R11
        CMP     R4,#-1
        MOVEQ   R4,#crf_invisible       ; if no flags supplied, make the caret invisible
        TSTNE   R4,#crf_ghostcaret
        ADREQL  R11,caretdata
        ADRNEL  R11,ghostcaretdata

        ; update stored data block
        STMIA   R11,{R0-R5}
        TST     R4,#crf_ghostcaret      ; if we're a ghost caret we don't do messages
        BNE     %FT21

        LDR     R14,oldcaretwindow
        CMP     R0,R14
        BEQ     %FT21                   ; same window, no repeat messages
        BL      send_gaincaret
        BL      check_shaded_selection_window
        BL      focuson
21
        MOVVS   R14,#nullptr            ; if not successful, clear the caret block
        STRVS   R14,[R11,#0]
        B       exitsetcaret
22
        MOV     R14,#nullptr
        CMP     R4,#-1
        TSTNE   R4,#crf_ghostcaret
        ADREQL  R11,caretdata           ; upcaret may corrupt R11
        ADRNEL  R11,ghostcaretdata
        STR     R14,[R11,#0]
        B       exitsetcaret
40
        ; we are updating a selection caret
        BL      wscp_remove_current_caret
        BVS     %FT49

        ; need to sort out the positions of the start and end indices
        CMP     R5,R6
        BGT     exitsetcaret            ; invalid range, so abort
41
        ; is the icon handle valid?
        LDR     R14,[handle,#w_nicons]
        CMP     R1,R14                  ; icon handle out of range?
        BHS     exitsetcaret            ; abort if so

        LDR     R14,[handle,#w_taskhandle]
        Task    R14,,"SetCaret"         ; page in window owner task

        BL      wscp_make_offsets_valid ; ensure R5,R6 within string bounds

        Rel     R11,handle
        BL      clipboard_check_current_drag_op ; if we're messing with a drag selection, abort the drag

        CMP     R5,R6                   ; if we're the same, we need to remove the selection
        BNE     %FT41

        ; r5=r6, so remove the selection for now
        LDR     R11,[handle,#w_seldata+wselicon] ; keep icon handle for later...
        MOV     R14,#nullptr
        STR     R14,[handle,#w_seldata+wselicon]
        ; was the selection the selection window?  If so, remove that
        LDR     R0,selectionwindow
        Abs     R0,R0
        TEQ     R0,handle
        STREQ   R14,selectionwindow

        ; do we need to recalculate the normal caret?
        LDR     R1,caretdata
        Abs     R1,R1
        EOR     R1,R1,handle
        LDR     R2,caretdata+careticon
        EOR     R1,R1,R2
        EORS    R1,R1,R11
        BLEQ    refreshcaret_main       ; it's the same icon

        MOV     R0,R11
        MOV     R1,#0
        MOV     R2,#0
        BL      int_set_icon_state
        B       exitsetcaret
41
        Push    "R0,R11"
        LDR     R11,[handle,#w_icons]
        ADD     R11,R11,R1,LSL #i_shift   ; icon address
        BL      writable_calc_checksum
        STR     R0,[handle,#w_seldata+wselchecksum]    ; store checksum
        Pull    "R0,R11"

        STR     R1,[handle,#w_seldata+wselicon] ; store icon handle
        STR     R5,[handle,#w_seldata+wsellowindex] ; store start offset
        STR     R6,[handle,#w_seldata+wselhighindex] ; store end offset

        Push    "R2"                    ; we may need this later...

        ; start index
        ; convert to X coord and also obtain height
        ; we lose flags from R4... preserve these

        MOV     R11,R4
        ADD     R14,handle,#w_seldata+wselxoff
        STR     R14,caretx_dest
        BL      setcaretcoords
        ; sets r2,r3
        ; keep r2,r3
        ;str r2,[handle,#w_seldata+wselxoff]
        STR     R3,[handle,#w_seldata+wselyoff]
        ; keep flags but add in the caret height part
        BIC     R11,R11,#&ff00
        BIC     R11,R11,#&00ff
        ORR     R4,R11,R4
        STR     R4,[handle,#w_seldata+wselflags] ; store flags in w_seldata

        ; do the end index
        LDR     R5,[handle,#w_seldata+wselhighindex]  ; get end index as R6 has been corrupted
        ADD     R14,handle,#w_seldata+wselwidth
        STR     R14,caretx_dest
        BL      setcaretcoords

        LDR     R14,[handle,#w_seldata+wselwidth]
        LDR     R2,[handle,#w_seldata+wselxoff]
        SUB     R14,R14,R2
        STR     R14,[handle,#w_seldata+wselwidth] ; make a width

        ; do we want to override the scrollx with a user value?
        Pull    "R2"
        LDR     R4,[sp,#(4+1)*4]        ; get original R4 from entry to see if we have crf_nocentre

        TST     R4,#crf_nocentre
        MOVEQ   R2,#bignum

        STR     R2,[handle,#w_seldata+wselxoverride] ; store scrollx directly
        ; does this window also have the current input focus?
        ; if not, then we need to transfer that too
        LDR     R14,caretdata
        Abs     R14,R14
        TEQ     handle,R14

        MOVNE   R14,#2
        STRNE   R14,refreshmaincaret           ; refresh main caret by moving it to selection spot

        ; redraw the icon to reflect changes
        Rel     R14,handle
        STR     R14,selectionwindow
        LDR     R0,[handle,#w_seldata+wselicon]
        MOV     R1,#0
        MOV     R2,#0
        BL      int_set_icon_state
        B       exitsetcaret
49
        ; failed to set caret
        ; clean selection block and exit
        MOV     R14,#nullptr
        STR     R14,selectionwindow
        STR     R14,[handle,#w_seldata+wselicon]
        ; fall through...

exitsetcaret
        SavePSR R6
        Push    "R6"
exitsetcaret_abort
        CheckAllWindows "int_set_caret_position (after)"

        ; do we need to recalculate the main caret?
        LDR     R14,refreshmaincaret
        TEQ     R14,#1
        BLEQ    refreshcaret_main
00
        ; redraw anything queued
        LDR     R10,redrawcaretwindow
        CMP     R10,#nullptr
        LDRNE   R0,redrawcareticon
        CMPNE   R0,#nullptr
        BEQ     %FT02                   ; nothing to redraw
        Abs     R10,R10
        MOV     R1,#0
        MOV     R2,#0
        LDR     R14,[R10,#w_taskhandle]
        CMP     R14,#-1                 ; owned by a menu?
        LDREQ   R14,menutaskhandle
        CMPEQ   R14,#-4                 ; if it's -4 the menu is being deleted, so don't try to redraw
        BEQ     %FT02
01
        Task    R14,,"SetCaret"
        BL      int_set_icon_state
02
        ; and return
        Pull    "R6"                    ; get saved PSR
        Pull    "R14"                   ; taskhandle is remembered
        Task    R14,,"Restoring after SetCaret"

        ; did we need to move input focus to match the selection?
        LDR     R14,refreshmaincaret
        TEQ     R14,#2
        BEQ     %FT01
        RestPSR R6
        Pull    "R0-R7,R11,PC"          ; no, continue as normal by exiting
01
        ; yes.  We've finished our things, so we can re-call set_caret_position to set the input focus.
        ; that way losefocus/gainfocus messages etc will be sent properly and so on

        ; input focus goes to end point of selection, so
        LDMFD   R13,{R0-R7,R11}

        MOV     R5,R6                   ; index into string is now the end point
        BIC     R4,R4,#crf_selection :OR: crf_nocentre ; was selection, but make it the main caret
        ; everything else can stay the same
        BL      int_set_caret_position  ; back round again for another go

        RestPSR R6
        Pull    "R0-R7,R11,PC"          ; finally quit

wscp_remove_current_caret ROUT
; on entry, r0=window handle or -1
;           r1=icon handle if r0!=-1
;           r4=flags

        Push    "R0-R5,R10,R11,R14"
        ; ensure we're removing from a valid window
        Push    "handle"
        MOV     handle,R0
        BL      checkhandle
        Pull    "handle"
        MOVVS   R0,#nullptr             ; handle was invalid so change it to 'no window'

        ; clear redraw block
        MOV     R14,#nullptr
        STR     R14,redrawcaretwindow
        STR     R14,redrawcareticon

        MOV     R14,#0
        STR     R14,refreshmaincaret

        ; have we changed to a different window for the main caret?
        CMP     R4,#-1                  ; invalid flags? assume main caret
        BEQ     %FT00

        TST     R4,#crf_selection :OR: crf_ghostcaret
        BNE     %FT01

00      ; we are dealing with the main caret, so do the checks
        ; is there a current selection in this window?
        CMP     R0,#-1                ; no current window
        LDRNE   R14,[R10,#w_seldata+wselicon]
        CMPNE   R14,#-1               ; any current selection?
        CMPNE   R14,R1                ; in the same icon as the new one?
        BEQ     %FT50

        ; remove current selection and redraw its icon
        Push    "R0-R2,R10,R11"
        MOV     R0,R14
        MOV     R14,#-1
        STR     R14,[R10,#w_seldata+wselicon]
        MOV     R1,#0
        MOV     R2,#0
        BL      int_set_icon_state
        Pull    "R0-R2,R10,R11"

50
        LDR     R14,caretdata
        CMP     R0,R14
        BEQ     %FT01
        Push    "R0"
        MOV     R0,R14
        BL      focusoff                ; preserves flags
        BL      send_losecaret
        BL      check_unshaded_selection_window
        Pull    "R0"
01
        ; continue.
        ; need to pull a caret or a selection...
        CMP     R4,#-1                  ; invalid flags
        BEQ     %FT02
        TST     R4,#crf_selection
        BNE     wscp_rcc_selection_redraw
02
        ; we are removing one of the carets
        ; is it an icon?  If so, then the caret will be obliterated and redrawn in the new writeable icon redraw code
        ; so we can just request a redraw of the initial icon if it's different at this point

        ; work out which block we're playing with - main or ghost caret
        CMP     R4,#-1
        TSTNE   R4,#crf_ghostcaret
        ADREQL  R11,caretdata
        BEQ     %FT03

        ; ghost caret is on the move
        ADRL    R11,ghostcaretdata
        ; if current ghost caret is same as current main caret, we'll need to refresh the main caret
        LDR     R0,caretdata
        LDR     R1,caretdata+careticon
        LDR     R2,ghostcaretdata
        LDR     R3,ghostcaretdata+ghostcareticon
        EOR     R14,R0,R1
        EOR     R14,R14,R2
        EORS    R14,R14,R3
        MOVEQ   R14,#1
        MOVNE   R14,#0
        STR     R14,refreshmaincaret    ; set up redraw on exit...
03
        ; is there a previous window in use?
        ; if not, we can bail out
        LDR     R0,[R11,#0]             ; get window handle for current caret
        CMP     R0,#nullptr
        Pull    "R0-R5,R10,R11,PC",EQ

        ; is there a previous icon?
        LDR     R14,[R11,#4]            ; get icon handle for current caret
        CMP     R14,#0
        BGE     wscp_rcc_queue_redraw
        ; request screen redraw for that area - this will remove the old caret by EOR
        LDMIA   R11,{R0-R4}             ; get previous caret state
        BL      upcaret

        Pull    "R0-R5,R10,R11,PC"

wscp_rcc_selection_redraw
        ; if main caret present, it will need refreshing...
        LDR     R5,caretdata
        LDR     R14,selectionwindow
        TEQ     R14,R5
        BNE     %FT60

        LDR     R5,caretdata+careticon
        LDR     R14,[handle,#w_seldata+wselicon]
        TEQ     R5,R14
        MOVEQ   R14,#1
        STREQ   R14,refreshmaincaret    ; main caret will be in the selection
60
        LDR     R10,selectionwindow
        CMP     R10,#nullptr
        BEQ     %FT91                   ; no unshaded selection window, so skip next bit

        CMP     R10,R0
        BEQ     %FT91                   ; moving within the same window

        Abs     R10,R10
        LDR     R14,[handle,#w_seldata+wselicon]  ; icon handle for redraw - current selection

        MOV     R5,#nullptr
        STR     R5,selectionwindow      ; clear current unshaded selection window
        MOV     R0,R14
        MOV     R1,#0
        MOV     R2,#0
        BL      int_set_icon_state      ; redraw that icon to make it shaded

        Pull    "R0-R5,R10,R11,PC"
91
        ; new window becomes the unshaded selection as it will also gain the input focus
        ; we may have things to do to any other selection in the new window
        ; retrieve window handle for the destination
        Pull    "R0"
        Abs     R10,R0
        LDR     R14,[handle,#w_seldata]
        CMP     R14,R1                  ; moving to a new icon?
        Pull    "R1-R5,R10,R11,PC",EQ   ; no, same as before.  So no need to redraw (we'll get redrawn later)
        STR     R0,redrawcaretwindow    ; queue window/icon for redraw
        STR     R14,redrawcareticon
        Pull    "R1-R5,R10,R11,PC"

wscp_rcc_queue_redraw ROUT
        ; r11=pointer to window handle (ie caret data block or similar)
        ; r14=icon handle from the current caret state
        LDR     R11,[R11]               ; get the window handle itself
        CMP     R10,R11                 ; new window is the same as old?
        BNE     %FT02                   ; redraw regardless

        CMP     R14,R1                  ; different icon?
        ; if the same, then we rely on the icon redraw to follow when we set the updated caret position...
        Pull    "R0-R5,R10,R11,PC",EQ
02
        ; it's a different icon, so we redraw the one we're leaving
        STR     R11,redrawcaretwindow
        STR     R14,redrawcareticon
        Pull    "R0-R5,R10,R11,PC"

wscp_make_offsets_valid
        Push    "R7,R14"

        ; if R5<=0, then don't need to alter this one
        CMP     R5,#0
        BLE     %FT03

        ; clamp R5 to string length
        MOV     R7,R5
        BL      wscp_clamp_length
        MOV     R5,R7
03
        ; do we need to adjust R6 too?
        TST     R4,#crf_selection
        Pull    "R7,PC",EQ              ; no, exit

        CMP     R5,#0
        MOVLT   R5,#0                   ; r5<0 is not valid for selections

        CMP     R6,#0
        Pull    "R7,PC",EQ              ; no need to clamp
        MOV     R7,R6
        BL      wscp_clamp_length
        MOV     R6,R7
        Pull    "R7,PC"

wscp_clamp_length
        ; clamp offset to string length
        ; On entry, r7=current offset
        ;            r0=window handle
        ;            r1=icon handle
        ; On exit, r7=clamped offset
        Push    "R8-R10,R14"

        LDR     R14,[handle,#w_nicons]
        CMP     R1,R14
        LDR     R10,[handle,#w_icons]
        ADD     R10,R10,R1,LSL #i_shift ; R10 now points to icon data block
        LDR     R9,[R10,#i_flags]
        TST     R9,#if_indirected
        MOVEQ   R9,#12
        ADDEQ   R8,R10,#i_data
        LDRNE   R9,[R10,#i_data+8]      ; max length
        LDRNE   R8,[R10,#i_data]
        ; r9=maximum string length
        ; r8=pointer to string data

        MOV     R14,#0                  ; current string length
79
        LDRB    R10,[R8,R14]
        CMP     R10,#32
        BLT     %FT80                   ; we have hit the end of the string

        SUBS    R9,R9,#1                ; any more allowed?
        BEQ     %FT80                   ; end of string

        ADD     R14,R14,#1              ; next byte

        CMP     R7,R14                  ; have we matched the caret offset yet?
        BNE     %BT79                   ; no - try again

        Pull    "R8-R10,PC"             ; yes - offset is within string
80
        ; end of string
        ; R7 can't be any greater than this
        MOV     R7,R14
        Pull    "R8-R10,PC"

check_unshaded_selection_window
        ; if the window losing the input focus has a selection, redraw it to ensure it becomes unshaded
        CMP     R0,#nullptr
        MOVEQ   PC,R14
        Push    "R0-R2,R10,R14"
        Abs     R10,R0
        LDR     R14,[handle,#w_seldata+wselicon]
        CMP     R14,#nullptr
        Pull    "R0-R2,R10,PC",EQ       ; nothing to do here

        MOV     R2,#0
        MOV     R1,#0
        MOV     R0,R14
        MOV     R14,#nullptr
        STR     R14,selectionwindow     ; no active selection right now...
        BL      int_set_icon_state

        Pull    "R0-R2,R10,PC"

check_shaded_selection_window
        ; if the window gaining the input focus has a selection, we need to make sure it is redrawn as active
        Push    "R0-R2,R11,R14"

        LDR     R14,[handle,#w_seldata+wselicon] ; selection icon
        CMP     R14,#nullptr
        Pull    "R0-R2,R11,PC",EQ       ; nothing here

        ; we have a selection
        ; update the selectionwindow
        Rel     R11,handle
        STR     R11,selectionwindow

        ; redraw the icon with the selection
        MOV     R2,#0
        MOV     R1,#0
        MOV     R0,R14
        BL      int_set_icon_state

        Pull    "R0-R2,R11,PC"

writable_calc_checksum
        ; calculate checksum on text from an icon
        ; In: r11 is pointer to icon block
        ; Out: r0 is checksum
        Push    "R1-R4,R14"
        ; get max string length
        LDR     R14,[R11,#i_flags]
        TST     R14,#if_indirected
        MOVEQ   R4,#12
        ADDEQ   R1,R11,#i_data
        LDRNE   R4,[R11,#i_data+8]
        LDRNE   R1,[R11,#i_data]

        ; r1 has string pointer
        ; r4 has max length in bytes

        ; get text string length
        MOV     R2,R1
70
        LDRB    R0,[R2],#1
        CMP     R0,#32
        BLT     %FT71

        SUBS    R4,R4,#1
        BNE     %BT70
71
        ; R1 points to start of block
        ; R2 now points to byte after end of block
        ; block potentially includes terminator, but not a problem
        MOV     R0,#0
        MOV     R3,#1
        SWI     XOS_CRC

        Pull    "R1-R4,PC"

; refresh caret states - recalculation of positions and redraw if there are changes
; needed as moving one caret may induce movement of another one.

refreshcaret_main
        Push    "R0-R5,R10,R11,R14"
        ADRL    R11,caretdata
        LDMIA   R11,{R0-R5}
        Abs     handle,R0

        ; make sure the task is paged in for checking
        LDR     R14,[handle,#w_taskhandle]
        Task    R14,,"Refreshcaret_main"

        ADRL    R14,caretx
        STR     R14,caretx_dest
        BL      setcaretcoords

        STR     R2,caretdata+caretxoff          ; update store
        STR     R3,caretdata+caretyoff
        Pull    "R0-R5,R10,R11,PC"

refreshcaret_ghost_main
        ; ghost caret has been updated
        ; if the main caret is in the same icon, it may have moved on screen (though string index unchanged)
        ; on entry, r0-r5 are the caret block as updated (ghost caret data)
        Push    "R14"
        LDR     R14,caretdata
        TEQ     R14,R0
        Pull    "PC",NE

        LDR     R14,caretdata+careticon
        TEQ     R14,R1
        Pull    "PC",NE

        ; same icon.  Do the recalculation.
        ADRL    R11,caretdata
        LDMIA   R11,{R0-R5}

        ADRL    R14,caretx
        STR     R14,caretx_dest
        BL      setcaretcoords

        STR     R2,caretdata+caretxoff          ; update store
        STR     R3,caretdata+caretyoff
        ; no need to redraw as we're redrawing due to ghost caret change already
        MOV     R14,#0
        STR     R14,refreshmaincaret    ; don't need to refresh again if we were planning to

        ; restore settings.
        ADRL    R11,ghostcaretdata
        LDMIA   R11,{R0-R5}
        Pull    "PC"

ghostcaret_checksum_fail
; the checksum failed, so we need to
; * remove ghost caret
; * remove selection if affected too
; * adjust screen coords of main caret if relevant
; Entry: r0=checksum
;        r10=window handle
;        Task already paged in
; Exit: all preserved
        Push    "R0-R1,R14"
        ; if selection is in same icon, it may also be affected
        LDR     R14,ghostcaretdata+ghostcareticon
        LDR     R1,[handle,#w_seldata+wselicon]
        TEQ     R1,R14
        MOV     R14,#nullptr
        BNE     %FT01                   ; not the same icon

        LDR     R1,[handle,#w_seldata+wselchecksum]
        TEQ     R0,R1
        BEQ     %FT01                   ; the checksum is actually valid for the selection, so leave it alone

        STR     R14,[handle,#w_seldata+wselicon] ; clear selection state
        STR     R14,selectioniconaddr

        ; current selection window as well?
        LDR     R0,selectionwindow
        Abs     R0,R0
        TEQ     R0,handle
        STREQ   R14,selectionwindow     ; if so, clear it
01
        ; check against the main caret
        LDR     R0,caretdata
        LDR     R1,ghostcaretdata
        STR     R14,ghostcaretdata      ; we can now reset this bit
        STR     R14,ghostcareticonaddr
        TEQ     R0,R1
        Pull    "R0-R1,PC",NE           ; differs
        LDR     R0,caretdata+careticon
        LDR     R1,ghostcaretdata+ghostcareticon
        TEQ     R0,R1
        Pull    "R0-R1,PC",NE

        ; recalc main caret
        Push    "R2-R11"
        ADRL    R14,caretdata
        LDMIA   R14,{R0-R5}             ; get current caret state
        ADRL    R14,caretx
        STR     R14,caretx_dest
        STR     R14,hasghostcaret       ; not any more

        ; do we still have the selection?
        LDR     R14,[handle,#w_seldata+wselicon]
        TEQ     R14,R1
        MOV     R14,#0
        STREQ   R14,hasselection        ; we have it
        STRNE   handle,hasselection     ; not us
        STR     R14,hascaret            ; still have this one

        BL      setcaretcoords          ; do the recalculation
        STR     R2,caretdata+caretxoff
        STR     R3,caretdata+caretyoff
        Pull    "R2-R11"
        Pull    "R0-R1,PC"

selectioncaret_checksum_fail
; the checksum failed so we need to
; * remove selection box
; * remove ghost caret if in same icon and its checksum is invalid
; * adjust screen coords of main caret if relevant
; Entry: r0=checksum
;        r10=window handle
;        Task already paged in
; Exit: all preserved
        Push    "R0-R1,R14"
        ; ghost caret in same icon?
        LDR     R14,ghostcaretdata
        Abs     R14,R14
        TEQ     R14,R10
        BNE     %FT01                   ; not same window
        LDR     R14,ghostcaretdata+ghostcareticon
        LDR     R1,[handle,#w_seldata]
        TEQ     R1,R14
        BNE     %FT01                   ; not same icon

        ; checksum
        LDR     R1,ghostcaretchecksum
        TEQ     R0,R1
        BEQ     %FT02                   ; checksum is actually valid so we leave the ghost caret alone
        ; note that ghost caret overrides main caret, so we don't recalculate main caret coords

        ; remove ghost caret
        MOV     R14,#nullptr
        STR     R14,ghostcaretdata
        STR     R14,ghostcareticonaddr
01
        LDR     R1,[handle,#w_seldata+wselicon] ; icon
        ; remove selection caret
        MOV     R14,#nullptr
        STR     R14,[handle,#w_seldata+wselicon]
        STR     R14,selectioniconaddr
        LDR     R0,selectionwindow
        Abs     R0,R0
        TEQ     R0,handle
        STREQ   R14,selectionwindow     ; no active selection if it was us

        LDR     R0,caretdata+careticon
        TEQ     R0,R1
        Pull    "R0-R1,PC",NE           ; not same icon

        LDR     R0,caretdata
        Abs     R0,R0
        TEQ     R0,handle
        Pull    "R0-R1,PC",NE           ; not same window

        ; recalc main caret
        Push    "R2-R11"
        ADRL    R14,caretdata
        LDMIA   R14,{R0-R5}             ; get current caret state
        ADRL    R14,caretx
        STR     R14,caretx_dest
        STR     R14,hasghostcaret       ; not any more as we're doing a main caret
        STR     R14,hasselection        ; definitely not

        MOV     R14,#0
        STR     R14,hascaret            ; still have this one

        BL      setcaretcoords          ; do the recalculation
        STR     R2,caretdata+caretxoff
        STR     R3,caretdata+caretyoff
        Pull    "R2-R11"
        Pull    "R0-R1,PC"
02
        MOV     R14,#nullptr
        STR     R14,[handle,#w_seldata+wselicon] ; clear selection
        STR     R14,selectioniconaddr
        LDR     R0,selectionwindow
        Abs     R0,R0
        TEQ     R0,handle
        STREQ   R14,selectionwindow     ; no active selection if it was us
        Pull    "R0-R1,PC"              ; return.  No update of main caret as the ghost caret overrides it anyway

clipboard_check_current_drag_op
        Push    "R10,R14"

        ; Entry: R11=window handle
        LDR     R10,clipboard_spritearea_addr
        TEQ     R10,#0
        Pull    "R10,PC",EQ             ; no drag system available
        ; check to see if we are currently dragging from this window
        ; if dragging==TRUE and drag_finished==FALSE (as drag_aborted==TRUE means drag_finished==TRUE too)
        LDRB    R14,[R10,#cbtask_var_dragging]
        TEQ     R14,#CNPTRUE
        LDREQB  R14,[R10,#cbtask_var_drag_finished]
        TEQEQ   R14,#CNPFALSE
        LDREQ   R14,[R10,#cbtask_var_source_window]
        TEQEQ   R14,R11
        ; if R11==dragging source
        ; abort the drag as the selection is being messed with
        MOVEQ   R14,#clipboard_pw_dragabort
        ADREQL  R10,clipboard_pollword
        STREQ   R14,[R10]

        Pull    "R10,PC"
 ]

        END