00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 package provide domtext 2.5
00018
00019
00020
00021 package require BWidget 1.4
00022
00023
00024
00025
00026 package require dom 3.2
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040 namespace domtext {
00041 Widget::tkinclude domtext text .text \
00042 remove {-command -state}
00043
00044 Widget::declare domtext {
00045 {-highlightcolor String "
00046 {-rootnode String "" 0}
00047 {-state String "normal" 0}
00048 {-tagcolor String "
00049 {-commentcolor String "
00050 {-entityrefcolor String "
00051 {-elementbgcolorlist String "" 0}
00052 {-showxmldecl Boolean 1 0}
00053 {-showdoctypedecl Boolean 1 0}
00054 {-showtag String "text" 0}
00055 }
00056
00057 ret ::domtext ( type path , type args ) { return [eval domtext::create $path $args] }
00058 ret use () {}
00059
00060
00061
00062
00063
00064 bind domtext <Button-1> [namespace code [list _tkevent_override %W %x %y]]
00065 bind domtext <Double-Button-1> [namespace code [list _tkevent_override %W %x %y]]
00066
00067
00068
00069
00070
00071
00072
00073 foreach spec {
00074 <Meta-Key-d> <Meta-Key-Delete> <Meta-Key-BackSpace>
00075 <Control-Key-h> <Control-Key-t> <Control-Key-k> <Control-Key-d>
00076 <Control-Key-i> <Key>
00077 <<Cut>> <<Paste>> <<PasteSelection>> <<Clear>>
00078 <Key-BackSpace> <Key-Delete> <Key-Return>
00079 } {
00080 bind domtext $spec [list domtext::_tkevent_filter_$spec %W %A]
00081 }
00082 foreach spec {
00083 <Key-Up> <Key-Down> <Key-Left> <Key-Right>
00084 } {
00085 bind domtext $spec [list domtext::_key_select %W $spec]
00086 }
00087 foreach spec {
00088 <Meta-Key> <Control-Key>
00089 } {
00090 bind domtext $spec {
00091 }
00092
00093 variable eventTypeMap
00094 array eventTypeMap = {
00095 ButtonPress mousedown
00096 ButtonRelease mouseup
00097 Enter mouseover
00098 Leave mouseout
00099 Motion mousemove
00100 FocusIn DOMFocusIn
00101 FocusOut DOMFocusOut
00102 }
00103 }
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116 ret domtext::create (type path , type args) {
00117 upvar #0 [namespace current]::$path data
00118 array set maps [list Text {} :text {} .text {}]
00119
00120 eval frame $path $maps(:text) -bd 0 -relief flat -takefocus 0 \
00121 -class domtext -highlightthickness 0
00122
00123 Widget::initFromODB domtext $path $maps(Text)
00124
00125 # Setup event bindings for generating DOM events
00126 bindtags $path [list $path Bwdomtext [winfo toplevel $path] all]
00127
00128 set text [eval text $path.text $maps(.text) \
00129 -state [Widget::getMegawidgetOption $path -state] -wrap none \
00130 -takefocus 1]
00131 $text tag configure starttab -elide 1
00132 $text tag configure endtab -elide 1
00133 $text tag configure xmldecl -elide 1
00134 $text tag configure doctypedecl -elide 1
00135
00136 bindtags $path [list $path domtext [winfo toplevel $path] all]
00137
00138 grid $text -sticky news
00139 grid rowconfigure $path 0 -weight 1
00140 grid columnconfigure $path 0 -weight 1
00141
00142 # Certain class bindings must be overridden
00143 bindtags $text [list $path domtext [winfo class $text] [winfo toplevel $path] all]
00144
00145 rename $path ::$path:cmd
00146 proc ::$path { cmd args } "return \[eval domtext::\$cmd $path \$args\]"
00147
00148 set root [Widget::getMegawidgetOption $path -rootnode]
00149 if {[string length $root]} {
00150 _refresh $path $root
00151 }
00152
00153 set data(insert) end
00154 set data(nextElemBgColor) 0
00155
00156 configure $path \
00157 -showtag [Widget::getMegawidgetOption $path -showtag] \
00158 -showxmldecl [Widget::getMegawidgetOption $path -showxmldecl] \
00159 -showdoctypedecl [Widget::getMegawidgetOption $path -showdoctypedecl]
00160
00161 return $path
00162 }
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175 ret domtext::cget (type path , type option) {
00176 return [Widget::getoption $path $option]
00177 }
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190 ret domtext::configure (type path , type args) {
00191 upvar #0 [namespace current]::$path data
00192
00193 set res [Widget::configure $path $args]
00194
00195 set rn [Widget::hasChanged $path -rootnode root]
00196 if {$rn} {
00197
00198 $path.text delete 1.0 end
00199 # Delete all marks and tags
00200 # This doesn't delete the standard marks and tags
00201 eval $path.text tag delete [$path.text tag names]
00202 eval $path.text mark unset [$path.text mark names]
00203 # Remove event listeners from previous DOM tree
00204
00205 set data(insert) 1.0
00206
00207 if {[string length $root]} {
00208 set docel [dom::document cget $root -documentElement]
00209
00210 if {[string length $docel]} {
00211 # Listen for UI events
00212 dom::node addEventListener $root DOMActivate [namespace code [list _node_selected $path]] -usecapture 1
00213
00214 # Listen for mutation events
00215 dom::node addEventListener $root DOMNodeInserted [namespace code [list _node_inserted $path]] -usecapture 1
00216 dom::node addEventListener $root DOMNodeRemoved [namespace code [list _node_removed $path]] -usecapture 1
00217 dom::node addEventListener $root DOMCharacterDataModified [namespace code [list _node_pcdata_modified $path]] -usecapture 1
00218 dom::node addEventListener $root DOMAttrModified [namespace code [list _node_attr_modified $path]] -usecapture 1
00219 dom::node addEventListener $root DOMAttrRemoved [namespace code [list _node_attr_removed $path]] -usecapture 1
00220
00221 _refresh $path $root
00222 }
00223 }
00224 }
00225
00226 set tc [Widget::hasChanged $path -tagcolor tagcolor]
00227 set hc [Widget::hasChanged $path -highlightcolor hlcolor]
00228 set cc [Widget::hasChanged $path -commentcolor commcolor]
00229 set ec [Widget::hasChanged $path -entityrefcolor ercolor]
00230 set ebg [Widget::hasChanged $path -elementbgcolorlist ebgcolor]
00231 if {($rn && [string length $root]) || $tc} {
00232 $path.text tag configure tags -foreground $tagcolor
00233 }
00234 if {($rn && [string length $root]) || $cc} {
00235 $path.text tag configure comment -foreground $commcolor
00236 }
00237 if {($rn && [string length $root]) || $ec} {
00238 $path.text tag configure entityreference -foreground $ercolor
00239 }
00240 if {($rn && [string length $root]) || $hc} {
00241 $path.text tag configure highlight -background $hlcolor
00242 }
00243 if {($rn && [string length $root]) || $ebg} {
00244 set data(nextElemBgColor) 0
00245 _elementbg_setall $path $root
00246 }
00247
00248 if {[Widget::hasChanged $path -showtag showtag]} {
00249 switch -- $showtag {
00250 text {
00251 $path.text tag configure starttab -elide 1
00252 $path.text tag configure endtab -elide 1
00253 $path.text tag configure tags -elide 0
00254 }
00255 tab {
00256 $path.text tag configure tags -elide 1
00257 $path.text tag configure starttab -elide 0
00258 $path.text tag configure endtab -elide 0
00259 }
00260 {} {
00261 $path.text tag configure tags -elide 1
00262 $path.text tag configure starttab -elide 1
00263 $path.text tag configure endtab -elide 1
00264 }
00265 default {
00266 return -code error "invalid value \"$showtag\""
00267 }
00268 }
00269 }
00270
00271 if {[Widget::hasChanged $path -showxmldecl showxmldecl]} {
00272 $path.text tag configure xmldecl -elide [expr !$showxmldecl]
00273 }
00274 if {[Widget::hasChanged $path -showdoctypedecl showdoctypedecl]} {
00275 $path.text tag configure doctypedecl -elide [expr !$showdoctypedecl]
00276 }
00277 return $res
00278 }
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291 ret domtext::xview (type path , type args) {
00292 eval $path.text xview $args
00293 }
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306 ret domtext::yview (type path , type args) {
00307 eval $path.text yview $args
00308 }
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329 ret domtext::_refresh (type path , type node) {
00330 upvar #0 [namespace current]::$path data
00331
00332 $path.text mark set $node $data(insert)
00333 $path.text mark gravity $node left
00334
00335 set end $data(insert)
00336
00337 # For all nodes we bind Tk events to be able to generate DOM events
00338 $path.text tag bind $node <1> [namespace code [list _tkevent_select $path $node %x %y]]
00339 $path.text tag bind $node <Double-1> [namespace code [list _tkevent_open $path $node]]
00340
00341 $path.text tag configure $node -background [_elementbg_cycle $path]
00342
00343 switch [::dom::node cget $node -nodeType] {
00344 document -
00345 documentFragment {
00346
00347 # Display the XML declaration
00348 if {0} {
00349 # OUCH! Need an interface in the DOM package for this data
00350 array set nodeInfo [set $node]
00351 # XML Declaration attributes have a defined order, so can't use array directly
00352 array set xmldecl $nodeInfo(document:xmldecl)
00353 set xmldecllist [list version $xmldecl(version)]
00354 catch {lappend xmldecllist standalone $xmldecl(standalone)}
00355 catch {lappend xmldecllist encoding $xmldecl(encoding)}
00356 $path.text insert $data(insert) "<?xml[dom::Serialize:attributeList $xmldecllist]?>\n" [list $node xmldecl]
00357 set data(insert) [lindex [$path.text tag ranges $node] end]
00358 }
00359 foreach childToken [::dom::node children $node] {
00360 set end [_refresh $path $childToken]
00361 set data(insert) $end
00362 }
00363
00364 $path.text tag add $node $node $end
00365 $path.text tag configure xmldecl -elide [expr ![Widget::cget $path -showxmldecl]]
00366 $path.text tag raise xmldecl
00367 }
00368
00369 element {
00370
00371 # Serialize the start tag
00372 $path.text insert $data(insert) <[::dom::node cget $node -nodeName] [list tags tag:start:$node] [_serialize:attributeList [array get [::dom::node cget $node -attributes]]] [list tags attrs:$node] > [list tags tag:start:$node]
00373
00374 # Add the start tab icon
00375 $path.text image create $data(insert) -image ::domtext::starttab -align center -name tab:start:$node
00376 foreach t [list starttab tags tag:start:$node] {
00377 $path.text tag add $t tab:start:$node
00378 }
00379
00380 set data(insert) [lindex [$path.text tag ranges tag:start:$node] end]
00381
00382 # Serialize the content
00383 $path.text mark set content:$node $data(insert)
00384 $path.text mark gravity content:$node left
00385 foreach childToken [::dom::node children $node] {
00386 set end [_refresh $path $childToken]
00387 set data(insert) $end
00388 }
00389 $path.text tag add content:$node content:$node $end
00390
00391 # Serialize the end tag
00392 $path.text insert $data(insert) </[::dom::node cget $node -nodeName]> [list tags tag:end:$node]
00393 set end [lindex [$path.text tag ranges tag:end:$node] end]
00394 # Add the end tab icon
00395 $path.text image create $end -image ::domtext::endtab -align center -name tab:end:$node
00396 foreach t [list endtab tags tag:end:$node] {
00397 $path.text tag add $t tab:end:$node
00398 }
00399 set end [lindex [$path.text tag ranges tag:end:$node] end]
00400
00401 set data(insert) $end
00402 $path.text tag add $node $node $end
00403
00404 $path.text tag raise starttab
00405 $path.text tag raise endtab
00406 $path.text tag configure starttab -elide [expr {[Widget::cget $path -showtag] != "tab"}]
00407 $path.text tag configure endtab -elide [expr {[Widget::cget $path -showtag] != "tab"}]
00408
00409 }
00410
00411 textNode {
00412 set text [_encode [dom::node cget $node -nodeValue]]
00413 if {[string length $text]} {
00414 $path.text insert $data(insert) $text $node
00415 set end [lindex [$path.text tag ranges $node] 1]
00416 set data(insert) $end
00417 } else {
00418 set end $data(insert)
00419 }
00420 }
00421
00422 docType {
00423 array set nodeInfo [set $node]
00424 $path.text insert $data(insert) "<!DOCTYPE $nodeInfo(doctype:name)" [list $node doctypedecl]
00425 set data(insert) [lindex [$path.text tag ranges $node] end]
00426
00427 if {[string length $nodeInfo(doctype:internaldtd)]} {
00428 $path.text insert $data(insert) " \[$nodeInfo(doctype:internaldtd)\]" [list $node doctypedecl]
00429 set data(insert) [lindex [$path.text tag ranges $node] end]
00430 }
00431
00432 $path.text insert $data(insert) >\n [list $node doctypedecl]
00433 set end [lindex [$path.text tag ranges $node] end]
00434 set data(insert) $end
00435 $path.text tag configure doctypedecl -elide [expr ![Widget::cget $path -showdoctypedecl]]
00436 $path.text tag raise doctypedecl
00437 }
00438
00439 comment {
00440 set text [::dom::node cget $node -nodeValue]
00441 $path.text insert $data(insert) <!-- [list comment markup $node] $text [list comment $node] --> [list comment markup $node]
00442 set end [lindex [$path.text tag ranges $node] 1]
00443 set data(insert) $end
00444 }
00445
00446 entityReference {
00447 set text [::dom::node cget $node -nodeName]
00448 $path.text insert $data(insert) & [list entityreference markup $node] $text [list entityreference $node] \; [list entityreference markup $node]
00449 set end [lindex [$path.text tag ranges $node] 1]
00450 set data(insert) $end
00451 }
00452
00453 processingInstruction {
00454 set text [::dom::node cget $node -nodeValue]
00455 if {[string length $text]} {
00456 set text " $text"
00457 }
00458 $path.text insert $data(insert) "<?[::dom::node cget $node -nodeName]$text?>" $node
00459 set end [lindex [$path.text tag ranges $node] 1]
00460 set data(insert) $end
00461 }
00462
00463 default {
00464 # Ignore it
00465 }
00466
00467 }
00468
00469 return $end
00470 }
00471
00472
00473
00474
00475
00476
00477
00478
00479
00480
00481
00482
00483
00484
00485 ret domtext::_serialize:attributeList atlist (
00486
00487 type set , type result , optional
00488 , type foreach , optional name =value $, type atlist , optional
00489
00490 append =result { $, type name =
00491
00492 # , type Handle , type special , type characters
00493 , type regsub -, type all & $, type value , optional \& , type value
00494 , type regsub -, type all < $, type value , optional \< , type value
00495
00496 , type if , optional ![string =match *\"* =$value] , optional
00497 append =result \"$value\"
00498 , type elseif , optional ![string =match *'* =$value] , optional
00499 append =result '$value'
00500 , type else , optional
00501 regsub =-all \" =$value {\" , type value
00502 , type append , type result \"$, type value\"
00503 )
00504
00505 }
00506
00507 return $result
00508 }
00509
00510 # domtext::_encode --
00511 #
00512 # Protect XML special characters
00513 #
00514 # NB. This is copied from TclDOM's domimpl.tcl.
00515 #
00516 # Arguments:
00517 # value text
00518 #
00519 # Results:
00520 # Returns string
00521
00522 proc domtext::_encode value {
00523 array set Entity {
00524 $ $
00525 < <
00526 > >
00527 & &
00528 \" "
00529 ' '
00530 }
00531
00532 regsub -all {([$<>&"'])} $value {$Entity(\1)} value
00533
00534 return [subst -nocommand -nobackslash $value]
00535 }
00536
00537 # domtext::_elementbg_setall --
00538 #
00539 # Recurse node hierarchy setting element background color property
00540 #
00541 # Arguments:
00542 # path widget path
00543 # node DOM node
00544 #
00545 # Results:
00546 # Text widget tag configured
00547
00548 proc domtext::_elementbg_setall {path node} {
00549
00550 $path.text tag configure $node -background [_elementbg_cycle $path]
00551
00552 switch [dom::node cget $node -nodeType] {
00553 document -
00554 documentFragment -
00555 element {
00556 foreach child [dom::node children $node] {
00557 _elementbg_setall $path $child
00558 }
00559 }
00560 default {
00561 /* No more to do here*/
00562 }
00563 }
00564
00565 return {}
00566 }
00567 ret domtext::_elementbg_cycle path (
00568 type upvar #0 [, type namespace , type current]::$, type path , type data
00569
00570 , type set , type list [, type Widget::, type cget $, type path -, type elementbgcolorlist]
00571 , type set , type colour [, type lindex $, type list $, type data(, type nextElemBgColor)]
00572
00573 , type set , type data(, type nextElemBgColor) [, type expr [, type incr , type data(, type nextElemBgColor)] % [, type llength $$, type list]]
00574
00575 , type return $, type colour
00576 )
00577
00578 # domtext::_node_inserted --
00579 #
00580 # React to addition of a node
00581 #
00582 # Arguments:
00583 # path widget path
00584 # evid DOM event node
00585 #
00586 # Results:
00587 # Display updated to reflect change to DOM structure
00588
00589 proc domtext::_node_inserted {path evid} {
00590 upvar /* 0 [namespace current]::$path data*/
00591
00592 node = [dom::event cget $evid -target]
00593
00594 /* Remove parent's content and then render new content*/
00595 parent = [dom::node parent $node]
00596 tags = [$path.text tag ranges $parent]
00597 start = [lindex $tags 0]
00598 end = [lindex $tags end]
00599 if {[string length $start]} {
00600 $path.text delete $start $end
00601 } else {
00602 start = end
00603 }
00604
00605 data = (insert) $start
00606 end = [_refresh $path $parent]
00607
00608
00609 parent = [::dom::node parent $parent]
00610 while {[string length $parent]} {
00611 ranges = [$path.text tag ranges $parent]
00612 catch {eval [list $path.text] tag remove [list $parent] $ranges}
00613 catch {$path.text tag add $parent [lindex $ranges 0] [lindex $ranges end]}
00614
00615 if {![string compare [::dom::node cget $parent -nodeType] "element"]} {
00616 ranges = [$path.text tag ranges content:$parent]
00617 catch {eval [list $path.text] tag remove [list $parent] $ranges}
00618 catch {$path.text tag add content:$parent [lindex $ranges 0] [lindex $ranges end]}
00619 }
00620
00621 parent = [::dom::node parent $parent]
00622 }
00623
00624 return {}
00625 }
00626
00627
00628
00629
00630
00631
00632
00633
00634
00635
00636
00637
00638
00639
00640 ret domtext::_node_removed (type path , type evid) {
00641 upvar #0 [namespace current]::selected$path selected
00642
00643 set node [dom::event cget $evid -target]
00644
00645 if {[info exists selected] && ![string compare $node $selected]} {
00646 unset selected
00647 }
00648
00649 # Remove parent's content and then render new content
00650 set parent [dom::event cget $evid -relatedNode]
00651 set tags [$path.text tag ranges $parent]
00652 set start [lindex $tags 0]
00653 set end [lindex $tags end]
00654 if {[string length $start]} {
00655 $path.text delete $start $end
00656 } else {
00657 set start end
00658 }
00659
00660 set data(insert) $start
00661 set end [_refresh $path $parent]
00662
00663 # Restore grandparent element tags
00664 set parent [::dom::node parent $parent]
00665 while {[string length $parent]} {
00666 set ranges [$path.text tag ranges $parent]
00667 catch {eval [list $path.text] tag remove [list $parent] $ranges}
00668 catch {$path.text tag add $parent [lindex $ranges 0] [lindex $ranges end]}
00669 # Also do content tag for elements
00670 if {![string compare [::dom::node cget $parent -nodeType] "element"]} {
00671 set ranges [$path.text tag ranges content:$parent]
00672 catch {eval [list $path.text] tag remove [list $parent] $ranges}
00673 catch {$path.text tag add content:$parent [lindex $ranges 0] [lindex $ranges end]}
00674 }
00675
00676 set parent [::dom::node parent $parent]
00677 }
00678
00679 return {}
00680 }
00681
00682
00683
00684
00685
00686
00687
00688
00689
00690
00691
00692
00693 ret domtext::_node_attr_modified (type path , type evid) {
00694
00695 set node [dom::event cget $evid -target]
00696
00697 set tags [$path.text tag ranges attrs:$node]
00698 if {[llength $tags]} {
00699
00700 # Remove previously defined attributes
00701
00702 foreach {start end} $tags break
00703 set existingTags [$path.text tag names $start]
00704 $path.text delete $start $end
00705 $path.text tag delete attrs:$node
00706
00707 } else {
00708 set tagStartEnd [lindex [$path.text tag ranges tag:start:$node] end]
00709 set start [$path.text index "$tagStartEnd - 1 char"]
00710 set existingTags [$path.text tag names $start]
00711 }
00712
00713 # Replace with current attributes
00714
00715 lappend existingTags attrs:$node
00716 $path.text insert $start [::dom::Serialize:attributeList [array get [::dom::node cget $node -attributes]]] $existingTags
00717
00718 return {}
00719 }
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732 ret domtext::_node_attr_removed (type path , type evid) {
00733 _node_attr_modified $path $evid
00734 }
00735
00736
00737
00738
00739
00740
00741
00742
00743
00744
00745
00746
00747 ret domtext::_node_pcdata_modified (type path , type evid) {
00748
00749 set node [dom::event cget $evid -target]
00750
00751 if {[string compare [dom::node cget $node -nodeType] "textNode"]} {
00752 return -code error "node is not a text node"
00753 }
00754
00755 # Remember where the insertion point is
00756 set insert [$path.text index insert]
00757
00758 # Remove previous text
00759 set ranges [$path.text tag ranges $node]
00760 set tags [$path.text tag names [lindex $ranges 0]]
00761 eval [list $path.text] delete $ranges
00762
00763 # Replace with new text
00764 $path.text insert [lindex $ranges 0] [dom::event cget $evid -newValue] $tags
00765
00766 # Restore insertion point
00767 $path.text mark set insert $insert
00768
00769 return {}
00770 }
00771
00772
00773
00774
00775
00776
00777
00778
00779
00780
00781
00782
00783 ret domtext::_node_selected (type path , type evid) {
00784 upvar #0 [namespace current]::selected$path selected
00785
00786 set node [dom::event cget $evid -target]
00787 set selected $node
00788
00789 catch {eval [list $path.text] tag remove sel [$path.text tag ranges sel]}
00790
00791 set ranges [$path.text tag ranges $node]
00792 if {[llength $ranges]} {
00793 eval [list $path.text] tag add sel $ranges
00794 }
00795
00796 $path.text mark set insert [lindex $ranges end]
00797
00798 return {}
00799 }
00800
00801
00802
00803
00804
00805
00806
00807
00808
00809
00810
00811
00812
00813 ret domtext::_tkevent_override (type w , type x , type y) {
00814 return -code break
00815 }
00816
00817
00818
00819
00820
00821
00822
00823
00824
00825
00826
00827
00828
00829
00830 ret domtext::_tkevent_select (type path , type node , type x , type y) {
00831 variable tkeventid
00832
00833 catch {after cancel $tkeventid}
00834 set tkeventid [after idle "
00835 dom::event postUIEvent [list $node] DOMActivate -detail 1
00836 dom::event postMouseEvent [list $node] click -detail 1
00837 [namespace current]::_tkevent_select_setinsert [list $path] [list $node] [::tk::TextClosestGap $path.text $x $y]
00838 "]
00839 return {}
00840 }
00841
00842
00843
00844 ret domtext::_tkevent_select_setinsert (type path , type node , type idx) {
00845 switch [::dom::node cget $node -nodeType] {
00846 textNode {
00847 # No need to change where the insertion point is going
00848 }
00849 element {
00850 # Set the insertion point to the end of the first
00851 # child textNode, or if none to immediately following
00852 # the start tag.
00853 set fc [::dom::node cget $node -firstChild]
00854 if {[string length $fc] && [::dom::node cget $fc -nodeType] == "textNode"} {
00855 set idx [lindex [$path.text tag ranges $fc] end]
00856 } else {
00857 set idx [lindex [$path.text tag ranges tag:start:$node] end]
00858 }
00859 }
00860 default {
00861 # Set the insertion point following the node
00862 set idx [lindex [$path.text tag ranges $node] end]
00863 }
00864 }
00865
00866 $path.text mark set insert $idx
00867 $path.text mark set anchor insert
00868 focus $path.text
00869
00870 return {}
00871 }
00872
00873
00874
00875
00876
00877
00878
00879
00880
00881
00882
00883
00884 ret domtext::_tkevent_open (type path , type node) {
00885 variable tkeventid
00886
00887 catch {after cancel $tkeventid}
00888 set tkeventid [after idle "
00889 dom::event postUIEvent [list $node] DOMActivate -detail 2
00890 dom::event postMouseEvent [list $node] click -detail 2
00891 "]
00892 return {}
00893 }
00894
00895
00896
00897
00898
00899
00900
00901
00902
00903
00904
00905
00906 ret domtext::_key_select (type path , type spec) {
00907 # Once the Text widget gets the focus, it receives the event.
00908 # We compensate for this here
00909 if {[winfo class $path] == "Text"} {
00910 set path [winfo parent $path]
00911 }
00912 upvar #0 [namespace current]::selected$path selected
00913
00914 set root [Widget::cget $path -rootnode]
00915
00916 # If selected node is a textNode move around the text itself
00917 # Otherwise markup has been selected.
00918 # Move around the nodes
00919
00920 switch -glob [dom::node cget $selected -nodeType],$spec {
00921 textNode,<Key-Up> {
00922 set ranges [$path.text tag ranges $selected]
00923 foreach {line char} [split [lindex $ranges 0] .] break
00924 set index [$path.text index insert]
00925 foreach {iline ichar} [split [lindex $index 0] .] break
00926 if {$line == $iline} {
00927 set new [dom::node parent $selected]
00928 } else {
00929 ::tk::TextSetCursor $path.text [::tk::TextUpDownLine $path.text -1]
00930 # The insertion point may now be in another node
00931 set newnode [_insert_to_node $path]
00932 if {[string compare $newnode $selected]} {
00933 dom::event postUIEvent $newnode DOMActivate -detail 1
00934 }
00935 return -code break
00936 }
00937 }
00938 textNode,<Key-Down> {
00939 set ranges [$path.text tag ranges $selected]
00940 foreach {line char} [split [lindex $ranges end] .] break
00941 set index [$path.text index insert]
00942 foreach {iline ichar} [split [lindex $index 0] .] break
00943 if {$line == $iline} {
00944 bell
00945 return {}
00946 } else {
00947 ::tk::TextSetCursor $path.text [::tk::TextUpDownLine $path.text 1]
00948 # The insertion point may now be in another node
00949 set newnode [_insert_to_node $path]
00950 if {[string compare $newnode $selected]} {
00951 dom::event postUIEvent $newnode DOMActivate -detail 1
00952 }
00953 return -code break
00954 }
00955 }
00956 textNode,<Key-Left> {
00957 set ranges [$path.text tag ranges $selected]
00958 set index [$path.text index insert]
00959 if {[$path.text compare $index == [lindex $ranges 0]]} {
00960 set new [dom::node cget $selected -previousSibling]
00961 if {![string length $new]} {
00962 set new [dom::node parent $selected]
00963 }
00964 } else {
00965 ::tk::TextSetCursor $path.text insert-1c
00966 return -code break
00967 }
00968 }
00969 textNode,<Key-Right> {
00970 set ranges [$path.text tag ranges $selected]
00971 set index [$path.text index insert]
00972 if {[$path.text compare $index == [lindex $ranges end]]} {
00973 set new [dom::node cget $selected -nextSibling]
00974 if {![string length $new]} {
00975 set new [dom::node parent $selected]
00976 }
00977 } else {
00978 ::tk::TextSetCursor $path.text insert+1c
00979 return -code break
00980 }
00981 }
00982
00983 *,<Key-Up> {
00984 set new [dom::node parent $selected]
00985 }
00986 *,<Key-Down> {
00987 set new [dom::node cget $selected -firstChild]
00988 if {![string length $new]} {
00989 bell
00990 return {}
00991 }
00992 }
00993 *,<Key-Left> {
00994 if {[dom::node parent $selected] == $root} {
00995 bell
00996 return {}
00997 }
00998 set new [dom::node cget $selected -previousSibling]
00999 if {![string length $new]} {
01000 set new [dom::node parent $selected]
01001 }
01002 }
01003 *,<Key-Right> {
01004 set new [dom::node cget $selected -nextSibling]
01005 if {![string length $new]} {
01006 set new [dom::node parent $selected]
01007 }
01008 }
01009 }
01010 if {![string length $new]} {
01011 bell
01012 }
01013
01014 dom::event postUIEvent $new DOMActivate -detail 1
01015
01016 return -code break
01017 }
01018
01019
01020
01021
01022
01023
01024
01025
01026
01027
01028
01029
01030
01031
01032 ret domtext::_tkevent_filter_<Key> (type path , type detail) {
01033 # Once the Text widget gets the focus, it receives the event.
01034 # We compensate for this here
01035 set code ok
01036 if {[winfo class $path] == "Text"} {
01037 set path [winfo parent $path]
01038 set code break
01039 }
01040 upvar #0 [namespace current]::selected$path selected
01041
01042 set index [$path.text index insert]
01043
01044 $path.text tag remove sel 0.0 end
01045
01046 # Take action depending upon which node type the event has occurred.
01047 # Possibilities are:
01048 # text node insert the text, update node
01049 # element If a text node exists as first child,
01050 # redirect event to it and make it active.
01051 # Otherwise create a text node
01052 # Document Type Declaration ignore
01053 # XML Declaration ignore
01054
01055 switch [dom::node cget $selected -nodeType] {
01056 element {
01057 set child [dom::node cget $selected -firstChild]
01058 if {[string length $child]} {
01059 if {[dom::node cget $child -nodeType] == "textNode"} {
01060 dom::event postUIEvent $child DOMActivate -detail 1
01061 dom::node configure $child -nodeValue [dom::node cget $child -nodeValue]$detail
01062 ::tk::TextSetCursor $path.text insert+1c
01063 focus $path.text
01064 return -code $code {}
01065 } else {
01066 bell
01067 return -code $code {}
01068 }
01069 } else {
01070 set child [dom::document createTextNode $selected $detail]
01071 dom::event postUIEvent $child DOMActivate -detail 1
01072 # When we return the new text node will have been
01073 # inserted into the Text widget
01074 set end [lindex [$path.text tag ranges $child] 1]
01075 $path.text mark set insert $end
01076 $path.text tag remove sel 0.0 end
01077 focus $path.text
01078 return -code $code {}
01079 }
01080 }
01081 textNode {
01082
01083 # We need to know where in the character data to insert the
01084 # character. This is hard, so instead allow the Text widget
01085 # to do the insertion then take all of the text and
01086 # set that as the node's value
01087
01088 $path.text insert insert $detail $selected
01089 $path.text see insert
01090 focus $path.text
01091 set ranges [$path.text tag ranges $selected]
01092 set newvalue [$path.text get [lindex $ranges 0] [lindex $ranges end]]
01093 dom::node configure $selected -nodeValue $newvalue
01094 return -code $code {}
01095
01096 }
01097 default {
01098 bell
01099 return -code $code {}
01100 }
01101 }
01102
01103 return -code $code {}
01104 }
01105
01106 ret domtext::_tkevent_filter_<Key-Return> (type path , type detail) {
01107 set code [catch {_tkevent_filter_<Key> $path \n} msg]
01108 return -code $code $msg
01109 }
01110 ret domtext::_tkevent_filter_<Control-Key-i> (type path , type detail) {
01111 set code [catch {_tkevent_filter_<Key> $path \t} msg]
01112 return -code $code $msg
01113 }
01114
01115 ret domtext::_tkevent_filter_<Control-Key-t> (type path , type detail) {
01116 return -code break
01117 }
01118
01119 ret domtext::_tkevent_filter_<Control-Key-h> (type path , type detail) {
01120 set code [catch {_tkevent_filter_<Key-Backspace> $path $detail} msg]
01121 return -code $code $msg
01122 }
01123 ret domtext::_tkevent_filter_<Key-BackSpace> (type path , type detail) {
01124 # Once the Text widget gets the focus, it receives the event.
01125 # We compensate for this here
01126 if {[winfo class $path] == "Text"} {
01127 set path [winfo parent $path]
01128 }
01129 upvar #0 [namespace current]::selected$path selected
01130
01131 switch [dom::node cget $selected -nodeType] {
01132 textNode {
01133 # If we're at the beginning of the text node stop here
01134 set ranges [$path.text tag ranges $selected]
01135 if {![llength $ranges] || [$path.text compare insert <= [lindex $ranges 0]]} {
01136 bell
01137 return -code break
01138 }
01139 }
01140 default {
01141 switch [tk_messageBox -parent [winfo toplevel $path] -title [mc {Confirm Delete Node}] -message [format [mc {Are you sure you want to delete a node of type %s?}] [dom::node cget $selected -nodeType]] -type okcancel] {
01142 ok {
01143 dom::node removeNode [dom::node parent $selected] $selected
01144 }
01145 cancel {
01146 return -code break
01147 }
01148 }
01149 }
01150 }
01151
01152 $path.text delete insert-1c
01153 $path.text see insert
01154
01155 _tkevent_filter_update $path
01156
01157 return -code break
01158 }
01159 ret domtext::_tkevent_filter_<Key-Delete> (type path , type detail) {
01160 # Once the Text widget gets the focus, it receives the event.
01161 # We compensate for this here
01162 if {[winfo class $path] == "Text"} {
01163 set path [winfo parent $path]
01164 }
01165 upvar #0 [namespace current]::selected$path selected
01166
01167 switch [dom::node cget $selected -nodeType] {
01168 textNode {
01169 # If we're at the beginning of the text node stop here
01170 set ranges [$path.text tag ranges $selected]
01171 if {[$path.text compare insert >= [lindex $ranges end]]} {
01172 bell
01173 return -code break
01174 }
01175 }
01176 default {
01177 switch [tk_messageBox -parent [winfo toplevel $path] -title [mc {Confirm Delete Node}] -message [format [mc {Are you sure you want to delete a node of type %s?}] [dom::node cget $selected -nodeType]] -type okcancel] {
01178 ok {
01179 dom::node removeNode [dom::node parent $selected] $selected
01180 }
01181 cancel {
01182 return -code break
01183 }
01184 }
01185 }
01186 }
01187
01188 $path.text delete insert
01189 $path.text see insert
01190
01191 _tkevent_filter_update $path
01192
01193 return -code break
01194 }
01195 ret domtext::_tkevent_filter_update path (
01196 type upvar #0 [, type namespace , type current]::, type selected$, type path , type selected
01197
01198 # , type Now , type update , type the , type DOM , type node', type s , type value
01199
01200 , type set , type ranges [$, type path., type text , type tag , type ranges $, type selected]
01201
01202 # , type If , type all , type text , type has , type been , type deleted , type then , type remove , type the , type node
01203 , type if , optional [llength =$ranges] , optional
01204 set =newtext [$path.text =get [lindex =$ranges 0] =[lindex $ranges =end]]
01205 dom::node =configure $selected =-nodeValue $newtext
01206 , type else , optional
01207 set =parent [dom::node =parent $selected]
01208 =dom::node removeNode =[dom::node parent =$selected] $selected
01209 =# Move =selection to =parent element, =rather than =removing selection
01210 =#unset selected
01211 =dom::event postUIEvent =$parent DOMActivate =-detail 1
01212
01213
01214 , type return , optional
01215 )
01216
01217 # This will delete from the insertion point to the end of the line
01218 # or text node, whichever is shorter
01219 # TODO: implement this
01220 proc domtext::_tkevent_filter_<Control-Key-k> {path detail} {
01221 return -code break
01222 }
01223 /* TODO: this will delete the word to the left of the insertion point*/
01224 /* (only within the text node)*/
01225 ret domtext::_tkevent_filter_<Meta-Key-Delete> (type path , type detail) {
01226 return -code break
01227 }
01228 ret domtext::_tkevent_filter_<Meta-Key-BackSpace> (type path , type detail) {
01229 _tkevent_filter_<Meta-Key-Delete> $path $detail
01230 }
01231
01232 /* Utilities*/
01233
01234 /* domtext::_insert_to_node --*/
01235 /* */
01236 /* Finds the DOM node for the insertion point*/
01237 /* */
01238 /* Arguments:*/
01239 /* path widget path*/
01240 /* */
01241 /* Results:*/
01242 /* Returns DOM token*/
01243
01244 ret domtext::_insert_to_node path (
01245 type set , type tags [$, type path., type text , type tag , type names , type insert]
01246 , type set , type newnode [, type lindex $, type tags , type end]
01247 , type while , optional ![dom::DOMImplementation =isNode $newnode] , optional
01248 set =tags [lreplace =$tags end =end]
01249 set =newnode [lindex =$tags end]
01250
01251 , type return $, type newnode
01252 )
01253
01254 ### Inlined images
01255
01256 image create photo ::domtext::starttab -data {
01257 R0lGODlhEAAYAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/M
01258 M//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8z
01259 zP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/
01260 M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xm
01261 zMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswA
01262 M8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
01263 zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkz
01264 M5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bM
01265 zGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZm
01266 M2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/
01267 zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZ
01268 MzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
01269 zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
01270 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAz
01271 zAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcA
01272 AFUAAEQAACIAABEAAADuAADdAAC7AACqAACIAAB3AABVAABEAAAiAAARAAAA
01273 7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3
01274 d1VVVURERCIiIhEREQAAACwAAAAAEAAYAAcIgwABCBxIsKBAfAjx2TNYMCHC
01275 hQwPOrwHkaFDhRQjXtR3L6PBix3teSR4USRHexUlJuTY8WRFkBQ7dsQ3sOS9
01276 kzNrOmR5M6dKhCFl3qP5EyPOoTpXymRJFABMkTKb2sSZL19ShDz1WSU5MeZW
01277 rglNfgWL9d5YsvjMRgRQte3ZtXABAggIADs=
01278 }
01279 image create photo ::domtext::endtab -data {
01280 R0lGODlhEAAYAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/M
01281 M//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8z
01282 zP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/
01283 M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xm
01284 zMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswA
01285 M8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
01286 zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkz
01287 M5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bM
01288 zGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZm
01289 M2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/
01290 zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZ
01291 MzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
01292 zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
01293 MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAz
01294 zAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcA
01295 AFUAAEQAACIAABEAAADuAADdAAC7AACqAACIAAB3AABVAABEAAAiAAARAAAA
01296 7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3
01297 d1VVVURERCIiIhEREQAAACwAAAAAEAAYAAcIgwABCBxIsKDBgvbwKcR3cGDC
01298 hQwb2rsHMaLBiQ8XHpx4T1/Fi/c4fiRob6K+kCMBlOx4r6VHiAPxtWwpEqZA
01299 mSFZZlQY0+XMlxpvzsxJ0SYAnCZRGsV50mVKnDRbpsyXL+fJnRYF5mvaMeXA
01300 qjWDFtyqVOzYrkYNVvWqlqrbhg0BAggIADs=
01301 }
01302
01303