The package
namespace eval ::textURL \
{
# exports
namespace export textURL
# packages
package require Tk
package provide TextURL 0.2
package provide textURL 0.2
# global vars
set () {}
# ---------------
# create text widget
# ---------------
proc textURL {w args} \
{
variable {}
set cmd [list text $w]
# init URL current ID
set ($w:urlID) 0
# default values
set newattr {-foreground navy -underline 1}
set oldattr {-foreground gray -underline 1}
set list {command enter leave range url tag type}
foreach key $list { set ($w:$key) "" }
# get args
foreach {key value} $args \
{
switch -glob -- $key \
{
# {# textURL options }
+* \
{
switch -glob $key \
{
+cmd -
+com* { set ($w:command) $value }
+ent* { set ($w:enter) $value }
+lea* { set ($w:leave) $value }
+old* { set oldattr $value }
+new* { set newattr $value }
default { error "unknown option \"$key\": should be +newcolor, +oldcolor, +command or +cmd" }
}
}
# {# text options }
default { lappend cmd $key $value }
}
}
# create & configure text
eval $cmd
# redefine ref
rename $w ::textURL::_$w
interp alias {} $w {} ::textURL::dispatch $w
# tags config
eval $w tag config newURL $newattr
eval $w tag config oldURL $oldattr
# bindings
$w tag bind oldURL <Enter> [namespace code [list tagEvent $w oldURL enter]]
$w tag bind oldURL <Leave> [namespace code [list tagEvent $w oldURL leave]]
$w tag bind oldURL <Motion> [namespace code [list move $w oldURL]]
$w tag bind newURL <Enter> [namespace code [list tagEvent $w newURL enter]]
$w tag bind newURL <Leave> [namespace code [list tagEvent $w newURL leave]]
$w tag bind newURL <Motion> [namespace code [list move $w newURL]]
bind $w <1> [namespace code [list checkURL $w]]
# return ref
return $w
}
# ---------------
# dispatch operation
# ---------------
proc dispatch {w operation args} \
{
switch -glob -- $operation \
{
# {# text operations }
bbo* - cge* - com* - con* - deg* - del* - dli* - dum* -
edi* - get - ima* - ind* - ins* - mar* - sca* - sea* -
see - tag - win* - xvi* - yvi* \
{ return [uplevel 1 [linsert $args 0 ::textURL::_$w $operation]] }
# {# textURL operations }
che* { set op ::textURL::checkURL }
to { set op ::textURL::toIndex }
tex* { set op ::textURL::textInsert }
url { set op ::textURL::urlInsert }
inv* { set op ::textURL::invokeURL }
default { error "unknown operation \"$operation\"" }
}
uplevel 1 [linsert $args 0 $op $w]
}
# ---------------
# get the current URL
# ---------------
proc getURL {w type} \
{
variable {}
# get current index
set current [$w index current]
# get corresponding range
foreach {start end} [$w tag nextrange $type $current] break
if {![info exists start] || $current < $start} \
{
foreach {start end} [$w tag prevrange $type $current] break
if {![info exists start] || $current >= $end} { return }
}
# save range & type
set ($w:range) [list $start $end]
set ($w:type) $type
# return url
return [$w get $start $end]
}
# ---------------
# event
# ---------------
proc tagEvent {w type event {url ""}} \
{
variable {}
set cursor ""
switch $event \
{
enter \
{
# entering an URL
if {$url == ""} { set url [getURL $w $type] }
set cursor hand2
set ($w:url) $url
set ($w:type) $type
eval _$w tag add URL $($w:range)
}
leave \
{
# leaving an URL
set url $($w:url)
set cursor ""
set ($w:url) ""
set ($w:type) ""
eval _$w tag remove URL $($w:range)
}
}
# set/reset cursor
_$w config -cursor $cursor
# invoke corresponding callback
set cmd $($w:$event)
if {$cmd != ""} { uplevel 1 $cmd $type $url }
}
# ---------------
# move inside URL
# ---------------
proc move {w type} \
{
variable {}
set oldrange $($w:range)
set url [getURL $w $type]
set newrange $($w:range)
# check if URL range changed
if {$newrange != $oldrange} \
{
# yes, simulate enter, leave events
set ($w:range) $oldrange
::textURL::tagEvent $w $type leave
set ($w:range) $newrange
::textURL::tagEvent $w $type enter $url
}
}
# ---------------
# check if URL
# ---------------
proc checkURL {w} \
{
variable {}
# get current URL type
set type $($w:type)
if {$type == ""} { return }
# get URL text
set url $($w:url)
if {$type == "newURL"} \
{ # new URL
foreach {tag value} $($w:urls:$url) break
foreach {start end} [$w tag ranges $tag] \
{ # transform new to old URLs
$w tag add oldURL $start $end
$w tag remove newURL $start $end
}
}
# invoke URL
invokeURL $w $url
}
# ---------------
# invoke URL
# ---------------
proc invokeURL {w url args} \
{
variable {}
set cmd $($w:command)
if {$cmd != ""} \
{
# get URL value
set value [lindex $($w:urls:$url) 1]
# invoke command
uplevel 1 $cmd $value $args
}
}
# ---------------
# set text index
# ---------------
proc toIndex {w index} \
{
variable {}
set ($w:index) $index
}
# ---------------
# insert text
# ---------------
proc textInsert {w text} \
{
variable {}
$w insert $($w:index) $text
}
# ---------------
# insert url
# ---------------
proc urlInsert {w url {value ""}} \
{
variable {}
if {[info exists ($w:urls:$url)]} \
{
# existing URL, get tag & value
foreach {tag value} $($w:urls:$url) break
} \
else \
{
# new URL, save tag & value
set tag URL[incr ($w:urlID)]
lappend ($w:urls:$url) $tag $value
}
$w insert $($w:index) $url [list $tag newURL]
}
}The demo
# ==============
# demo
# ==============
package require TextURL
namespace import ::textURL::textURL
proc myenter {type url} { .t tag config URL -underline 1 }
proc myleave {type url} { .t tag config URL -underline 0 }
proc mycmd {value} { tk_messageBox -message $value }
textURL .t -bd 1 -bg beige +enter myenter +leave myleave \
+cmd mycmd +new {-foreground red} +old {-foreground gray}
pack .t
.t to 1.end
.t text "a line with an "
.t url URL1 value1
.t text " inside\n"
.t to 2.end
.t text "a line with an "
.t url URL2 value2
.t text " inside\n"
.t to 3.end
.t text "a line with an "
.t url URL1 value1
.t text " inside\n"
