# xentry_proc.tcl --
# 
# 
# Voir info dans fichier xtext.tcl
# 

package provide xentry 2.0


## xentry_closestGap --
# 
# Given x and y coordinates, this procedure finds the closest boundary
# between characters to the given coordinates and returns the index
# of the character just after the boundary.
#
# Arguments:
# w - The entry window.
# x - X-coordinate within the window.
# 
# Ajout diam le D/18/01/03 pour compatibilité wish8.3/wish8.4
# tité de ::tk::EntryClosestGap
# 
proc xentry_closestGap {w x} {
    set pos [$w index @$x]
    set bbox [$w bbox $pos]
    if {($x - [lindex $bbox 0]) < ([lindex $bbox 2]/2)} {
        return $pos
    }
    incr pos
}

# xentry_button1 --
# This procedure is invoked to handle button-1 presses in entry
# widgets.  It moves the insertion cursor, sets the selection anchor,
# and claims the input focus.
#
# Arguments:
# w -           The entry window in which the button was pressed.
# x -           The x-coordinate of the button press.

proc xentry_button1 {w x} {
    global xentry

    set xentry(selectMode) char
    set xentry(mouseMoved) 0
    set xentry(pressX) $x
    # $w icursor [tkEntryClosestGap $w $x]
    $w icursor [xentry_closestGap $w $x]
    $w selection from insert
    if {[string compare "disabled" [$w cget -state]]} {focus $w}
    xentry_updateCursorSize $w
}

# xentry_mouseSelect --
# This procedure is invoked when dragging out a selection with
# the mouse.  Depending on the selection mode (character, word,
# line) it selects in different-sized units.  This procedure
# ignores mouse motions initially until the mouse has moved from
# one character to another or until there have been multiple clicks.
#
# Arguments:
# w -           The entry window in which the button was pressed.
# x -           The x-coordinate of the mouse.

proc xentry_mouseSelect {w x} {
    global xentry

    # set cur [tkEntryClosestGap $w $x]
    set cur [xentry_closestGap $w $x]
    set anchor [$w index anchor]
    if {($cur != $anchor) || (abs($xentry(pressX) - $x) >= 3)} {
        set xentry(mouseMoved) 1
    }
    switch $xentry(selectMode) {
        char {
            if {$xentry(mouseMoved)} {
                if {$cur < $anchor} {
                    $w selection range $cur $anchor
                } elseif {$cur > $anchor} {
                    $w selection range $anchor $cur
                } else {
                    $w selection clear
                }
            }
        }
        word {
            if {$cur < [$w index anchor]} {
                set before [tcl_wordBreakBefore [$w get] $cur]
                set after [tcl_wordBreakAfter [$w get] [expr {$anchor-1}]]
            } else {
                set before [tcl_wordBreakBefore [$w get] $anchor]
                set after [tcl_wordBreakAfter [$w get] [expr {$cur - 1}]]
            }
            if {$before < 0} {
                set before 0
            }
            if {$after < 0} {
                set after end
            }
            $w selection range $before $after
        }
        line {
            $w selection range 0 end
        }
    }
    if {$xentry(mouseMoved)} {
        $w icursor $cur
    }
    xentry_updateCursorSize $w
    update idletasks
}

# xentry_autoScan --
# This procedure is invoked when the mouse leaves an entry window
# with button 1 down.  It scrolls the window left or right,
# depending on where the mouse is, and reschedules itself as an
# "after" command so that the window continues to scroll until the
# mouse moves back into the window or the mouse button is released.
#
# Arguments:
# w -           The entry window.

proc xentry_autoScan {w} {
    global xentry
    set x $xentry(x)
    if {![winfo exists $w]} return
    if {$x >= [winfo width $w]} {
        $w xview scroll 2 units
        xentry_mouseSelect $w $x
    } elseif {$x < 0} {
        $w xview scroll -2 units
        xentry_mouseSelect $w $x
    }
    set xentry(afterId) [after 50 [list xentry_autoScan $w]]
}

# xentry_keySelect --
# This procedure is invoked when stroking out selections using the
# keyboard.  It moves the cursor to a new position, then extends
# the selection to that position.
#
# Arguments:
# w -           The entry window.
# new -         A new position for the insertion cursor (the cursor hasn't
#               actually been moved to this position yet).

proc xentry_keySelect {w new} {
    # puts "w=$w new=$new"
    if {![$w selection present]} {
        $w selection from insert
        $w selection to $new
    } else {
        $w selection adjust $new
    }
    $w icursor $new
    xentry_updateCursorSize $w
}

## xentry_insert --
# 
# Insert a string into an entry at the point of the insertion cursor.
# If there is a selection in the entry, and it covers the point of the
# insertion cursor, then delete the selection before inserting.
#
# Arguments:
# w -           The entry window in which to insert the string
# s -           The string to insert (usually just a single character)
# 
proc xentry_insert {w s} {
    if {![string length $s]}  { return } 
    
    if {[string equal "disabled" [$w cget -state]]}  { return } 
    
    xentry_pushState $w
    if {[xentry_hasSel $w]} {
        $w delete sel.first sel.last
    }
    $w insert insert $s
    xentry_updateCursorSize $w
}

## xentry_pasteSel --
# 
# This procedure sets the insertion cursor to the current mouse position,
# pastes the selection there, and sets the focus to the window.
#
# Arguments:
# w - The entry window.
# x - X position of the mouse (optionnal).
# 
proc xentry_pasteSel {w {x 0}} {
    global xentry

    # Si on veut copier sous la souris (mais j'aime pas)
    # 
    # $w icursor [xentry_closestGap $w $x]
    
    if {[string equal "disabled" [$w cget -state]]}  { return } 

    set txt ""
    catch {
        set txt [selection get -displayof $w]
    }
    if {[string length $txt]} {
        xentry_pushState $w
        catch {
            $w delete sel.first sel.last
        }
        $w insert insert $txt
        xentry_updateCursorSize $w
    }
}

## xentry_paste --
# 
#
# 
proc xentry_paste {w} {
    global xentry

    set txt ""
    catch {
        set txt [selection get -displayof $w  -selection CLIPBOARD]
    }

    if {[string length $txt]} {
        xentry_pushState $w
        catch {
            $w delete sel.first sel.last
        }
        $w insert insert $txt
        xentry_updateCursorSize $w
    }
}

## xentry_copy --
# 
# Le problème est que je souhaite :
# - que l'entrée soit presélectionnée quand elle appel la fenetre de 
#   remplacementsans que cela ai pour conséquence de désélectionner le text
#   (d'ou le -exportselection  0 par défaut)
# - mais je souhaite aussi que l'on puisse transmettre les info copiées ou 
#   coupées depuis cette entry !
# 
proc xentry_copy {w} {
    global xentry
    set txt [xentry_getSel $w]
    if {![string length $txt]} {
        return
    }
    # diam le 04/02/03 pour test 
    $w configure -exportselection 1
    clipboard clear -displayof $w
    clipboard append -displayof $w $txt
    # $w configure -exportselection 0
    xentry_updateCursorSize $w
}

## xentry_cut --
# 
#
# 
proc xentry_cut {w} {
    xentry_copy $w
    xentry_delSel $w
}


## xentry_backspace --
# Backspace over the character just before the insertion cursor.
# If backspacing would move the cursor off the left edge of the
# window, reposition the cursor at about the middle of the window.
#
# Arguments:
# w -           The entry window in which to backspace.
# 
proc xentry_backspace w {
    if {[$w selection present]} {
        # # $w delete sel.first sel.last
        xentry_delSel $w
    } else {
        set x [expr {[$w index insert] - 1}]
        if {$x >= 0} {
            xentry_pushState $w
            $w delete $x
        }
        if {[$w index @0] >= [$w index insert]} {
            set range [$w xview]
            set left  [lindex $range 0]
            set right [lindex $range 1]
            $w xview moveto [expr {$left - ($right - $left)/2.0}]
        }
        xentry_updateCursorSize $w
    }
}
## xentry_delete --
# delete selction, or delete char after insert
#
# Arguments:
# w -           The entry window in which to backspace.
# 
proc xentry_delete {w {x1 insert} {x2 end} } {
    if {[$w selection present]} {
        xentry_delSel $w
    } else {
        xentry_pushState $w
        $w delete insert
        xentry_updateCursorSize $w
    }
}

# xentry_seeInsert --
# Make sure that the insertion cursor is visible in the entry window.
# If not, adjust the view so that it is.
#
# Arguments:
# w -           The entry window.

proc xentry_seeInsert w {
    set c [$w index insert]
    if {($c < [$w index @0]) || ($c > [$w index @[winfo width $w]])} {
        $w xview $c
    }
    xentry_updateCursorSize $w
}

# xentry_setCursor -
# Move the insertion cursor to a given position in an entry.  Also
# clears the selection, if there is one in the entry, and makes sure
# that the insertion cursor is visible.
#
# Arguments:
# w -           The entry window.
# pos -         The desired new position for the cursor in the window.

proc xentry_setCursor {w pos} {
    xentry_unSelect $w
    $w icursor $pos
    $w selection clear
    xentry_seeInsert $w
}

## xentry_transpose -
# 
# This procedure implements the "transpose" function for entry widgets.
# It tranposes the characters on either side of the insertion cursor,
# unless the cursor is at the end of the line.  In this case it
# transposes the two characters to the left of the cursor.  In either
# case, the cursor ends up to the right of the transposed characters.
#
# Arguments:
# w -           The entry window.
# 
proc xentry_transpose w {
    set i [$w index insert]
    if {$i < [$w index end]} {
        incr i
    }
    set first [expr {$i-2}]
    if {$first < 0} {
        return
    }
    
    xentry_pushState $w
    set new [string index [$w get] [expr {$i-1}]][string index [$w get] $first]
    $w delete $first $i
    $w insert insert $new
    xentry_seeInsert $w
}

# xentry_nextWord --
# Returns the index of the next word position after a given position in the
# entry.  The next word is platform dependent and may be either the next
# end-of-word position or the next start-of-word position after the next
# end-of-word position.
#
# Arguments:
# w -           The entry window in which the cursor is to move.
# start -       Position at which to start search.

if {[string equal $tcl_platform(platform) "windows"]}  {
    proc xentry_nextWord {w start} {
        set pos [tcl_endOfWord [$w get] [$w index $start]]
        if {$pos >= 0} {
            set pos [tcl_startOfNextWord [$w get] $pos]
        }
        if {$pos < 0} {
            return end
        }
        return $pos
    }
} else {
    proc xentry_nextWord {w start} {
        set pos [tcl_endOfWord [$w get] [$w index $start]]
        if {$pos < 0} {
            return end
        }
        return $pos
    }
}

# xentry_previousWord --
#
# Returns the index of the previous word position before a given
# position in the entry.
#
# Arguments:
# w -           The entry window in which the cursor is to move.
# start -       Position at which to start search.

proc xentry_previousWord {w start} {
    set pos [tcl_startOfPreviousWord [$w get] [$w index $start]]
    if {$pos < 0} {
        return 0
    }
    return $pos
}

## xentry_getSel --
#
# Returns the selected text of the entry with respect to the -show option.
#
# Arguments:
# w -         The entry window from which the text to get
# 
proc xentry_getSel {w} {
    if {![xentry_hasSel $w]} { return ""}
    
    set sel [string range [$w get] \
                          [$w index sel.first] \
                          [expr {[$w index sel.last] - 1}]]
                       
    if {[string compare [$w cget -show] ""]} {
        regsub -all . $sel [string index [$w cget -show] 0] sel
    }
    return $sel
}

# xentry_tkCancelRepeat --
# This procedure is invoked to cancel an auto-repeat action described
# by xentry(afterId).  It's used by several widgets to auto-scroll
# the widget when the mouse is dragged out of the widget with a
# button pressed.
#
# Arguments:
# None.

proc xentry_tkCancelRepeat {} {
    global xentry
    after cancel $xentry(afterId)
    set xentry(afterId) {}
}

########################################################################
## Ce qui suit vient de stead36 (avec modif éventuelles)
########################################################################

## xentry_updateCursorSize -- met à jour la largeur du curseur
# 
# La taille cursor est nulle en cas de selection 
# Si cette procédure n'est pas appeler quand la sélection disparait :
# le curseur est invisible !
# A REFAIRE SUR LE MODEL de xtext_updateCursor (ou ~)
# 
proc xentry_updateCursorSize {w} {
    # puts $w
    global EDITPREFS
            
        
    # # sauvegarde du point d'insertion et de la selection
    # set sel1 {}
    # set sel2 {}
    # set ins [$w index insert]
    # catch {
    #     set sel1 [$w index sel.first]
    #     set sel2 [$w index sel.last]
    # }
    
    # affichage du curseur selon l'etat
    # BUG (diam 20/01/2003):     
    #   Je n'arrive pas à supprimer l'insert par -insertwidth 0
    #   Je modifie donc le -insertontime 2000
    if { [xentry_hasSel $w] } {
        $w configure -insertwidth 0
        $w configure -insertontime 2000
        $w configure -insertofftime 0
    } else { 
        $w configure -insertwidth $EDITPREFS(textiw)
        $w configure -insertontime  500
        $w configure -insertofftime 500
    }
    
    # # restauration du point d'insertion et de la selection
    # if {"x$sel1" != "x"} then {
    #     $w select from $sel1
    #     $w select to $sel2
    # }  
    # $w icursor $ins
}

## xentry_hasSel -- vrai si l'entry contient une sélection
# 
# Cette procédure tres simple n'existe que pour assurer une cohérence avec 
# la procédure similaire de xtext.
# 
# TODO (peut-etre) : faut-il intégrer xentry_hasSel à xtext_hasSel ?
# 
proc xentry_hasSel {w} {
    return [$w selection present]
}

## xentry_delSel -- supprime la partie selectionnée dans l'entry
# 
# 
proc xentry_delSel {w} {
   global xtext
    
   set xtext(selectMode) char
   if { [xentry_hasSel $w] } {

      xentry_pushState $w
      
      # set xtext(buf,oldSel,data)  [xtext_getSel $w]
      # catch {$w delete sel.first sel.last} 
      
      $w delete sel.first sel.last
      $w selection clear
        
      xentry_updateCursorSize $w
   }
}

## xentry_unSelect -- déselectionne l'entry
# 
proc xentry_unSelect {w} {
    global xentry
    
    set xentry(selectMode) char
    if {![xentry_hasSel $w]} {
        return
    }
    $w select clear
    # $w tag remove sel 1.0 end
    xentry_updateCursorSize $w
}

## xentry_selectAll -- sélectionne entièrement l'entry
# 
proc xentry_selectAll {w} {
    $w select clear
    $w select from 0
    $w select to end
    xentry_updateCursorSize $w
}

########################################################################
########################################################################
########################################################################


## xentry_pushState -- save current state in XEntry undo ring
# 
# REMPLACE te:entry:cmd:save_checkpoint
# 
proc xentry_pushState {w} {
    upvar #0 ${w}(states,pointer)  p
    upvar #0 ${w}(states,text,$p)  text
    upvar #0 ${w}(states,state,$p) state
          
    set text  [$w get]
    set state [xentry_getState $w]
    incr p

    # On limite la pile du undo à 100 (arbitrairement pour l'instant)
    # on supprime dans toute données mémorisée trop ancienne
    if {$p > 100} {
        set old_p [expr {$p - 100}]
        unset ::${w}(states,text,$old_p) 
        unset ::${w}(states,state,$old_p)    
    }
} 

## xentry_undo -- 
# 
# NEW : remplace te:entry:cmd:undo
# 
proc xentry_undo {w} {
    upvar $w data

    upvar #0 ${w}(states,pointer)  p
    upvar #0 ${w}(states,text,$p)  text
    upvar #0 ${w}(states,state,$p) state

    # On mémorise l'état courent au cas ou l'on ferra un redo !
    set text  [$w get]
    set state [xentry_getState $w]    
    
    incr p -1
    upvar #0 ${w}(states,text,$p)  text
    upvar #0 ${w}(states,state,$p) state
    
    if {![info exists text]} {
        incr p
        set data(msg)  "No more undo information available for entry."
    } else {
        $w delete 0 end
        $w insert end $text
        xentry_applyState $w $state
        xentry_updateCursorSize $w
    }
}

## xentry_redo -- 
# 
# NEW : remplace te:entry:cmd:redo
# 
proc xentry_redo {w} {
    upvar $w data

    upvar #0 ${w}(states,pointer)  p
    upvar #0 ${w}(states,text,$p)  text
    upvar #0 ${w}(states,state,$p) state

    set text [$w get]
    set state [xentry_getState $w]
    
    incr p 
    upvar #0 ${w}(states,text,$p)  text
    upvar #0 ${w}(states,state,$p) state
    
    if {![info exists text]} {
        incr p -1
        set data(msg) "No more redo information available for entry."
    } else {
        $w delete 0 end
        $w insert end $text
        xentry_applyState $w $state
    }
}


## xentry_getState -- retourne le triplet "state" courent l'entry
# 
# Anciennement : te:entry:get_state
# 
proc xentry_getState {w} {
    set sel1 {}
    set sel2 {}
    set ins [$w index insert]
    if {[xentry_hasSel $w]} {
        set sel1 [$w index sel.first]
        set sel2 [$w index sel.last]
    }
    # # catch {set sel1 [$w index sel.first]; set sel2 [$w index sel.last]}
    return [list $ins $sel1 $sel2]
} 

## xentry_applyState -- applique le triplet "state" à l'entry
# 
# Anciennement : te:entry:set_state
# 
proc xentry_applyState { w state } {
    set ins  [lindex $state 0]
    set sel1 [lindex $state 1]
    set sel2 [lindex $state 2]
    if {"x$sel1" != "x"} then {
        $w select from $sel1
        $w select to $sel2
    }  
    $w icursor $ins
    xentry_updateCursorSize $w
} 
# ./
