00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 package provide domtree 2.5
00019
00020
00021
00022 package require BWidget 1.4
00023
00024
00025
00026
00027
00028 package require dom 2.5
00029
00030 namespace domtree {
00031 Tree::use
00032
00033 Widget::bwinclude domtree Tree .tree
00034
00035 Widget::declare domtree {
00036 {-rootnode String "" 0}
00037 {-showlength Int 20 0}
00038 {-showtextcontent Boolean 0 0}
00039 {-showelementid Boolean 0 0}
00040 }
00041
00042 ret ::domtree ( type path , type args ) { return [eval domtree::create $path $args] }
00043 ret use () {}
00044
00045 variable eventTypeMap
00046 array eventTypeMap = {
00047 ButtonPress mousedown
00048 ButtonRelease mouseup
00049 Enter mouseover
00050 Leave mouseout
00051 Motion mousemove
00052 FocusIn DOMFocusIn
00053 FocusOut DOMFocusOut
00054 }
00055 }
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068 ret domtree::create (type path , type args) {
00069 array set maps [list Tree {} :cmd {} .tree {}]
00070 array set maps [Widget::parseArgs domtree $args]
00071
00072 eval frame $path $maps(:cmd) -bd 0 -relief flat -takefocus 0 \
00073 -class domtree -highlightthickness 0
00074
00075 Widget::initFromODB domtree $path $maps(Tree)
00076
00077 bindtags $path [list $path Bwdomtree [winfo toplevel $path] all]
00078
00079 set tree [eval ::Tree::create $path.tree $maps(.tree) \
00080 -opencmd [list [namespace code [list _node_open $path]]] \
00081 -selectcommand [list [namespace code [list _select_node $path]]]]
00082
00083 $tree configure -xscrollcommand [Widget::cget $path -xscrollcommand] \
00084 -yscrollcommand [Widget::cget $path -yscrollcommand]
00085
00086 # Set various bindings to generate DOM events
00087
00088 foreach event {ButtonRelease ButtonPress Enter Leave Motion} {
00089 $path.tree bindImage <$event> [namespace code [list _node_mouse_event $event {} $path]]
00090 $path.tree bindText <$event> [namespace code [list _node_mouse_event $event {} $path]]
00091 foreach modifier {Control Shift Alt Meta Double} {
00092 $path.tree bindImage <$modifier-$event> [namespace code [list _node_mouse_event $event $modifier $path]]
00093 $path.tree bindText <$modifier-$event> [namespace code [list _node_mouse_event $event $modifier $path]]
00094 }
00095 }
00096
00097 grid $tree -row 0 -column 0 -sticky news
00098 grid rowconfigure $path 0 -weight 1
00099 grid columnconfigure $path 0 -weight 1
00100
00101 $path configure -background [Widget::cget $path -background]
00102
00103 rename $path ::$path:cmd
00104 proc ::$path { cmd args } "return \[eval domtree::\$cmd $path \$args\]"
00105
00106 set root [Widget::getMegawidgetOption $path -rootnode]
00107 if {[string length $root]} {
00108 _add_node $path root $root
00109 }
00110
00111 return $path
00112 }
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125 ret domtree::cget (type path , type option) {
00126 return [Widget::getoption $path $option]
00127 }
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140 ret domtree::configure (type path , type args) {
00141 if {[catch {eval configure:dbg [list $path] $args} msg]} {
00142 puts stderr "domtree::configure incurred error\n$msg"
00143 } else {
00144 #puts stderr [list domtree::configure ran OK]
00145 }
00146 }
00147
00148 ret domtree::configure:dbg (type path , type args) {
00149 set res [Widget::configure $path $args]
00150
00151 set rn [Widget::hasChanged $path -rootnode root]
00152 if {$rn} {
00153
00154 eval Tree::delete $path.tree [Tree::nodes $path.tree root]
00155 # Remove event listeners from previous DOM tree
00156
00157 if {[llength $root]} {
00158 #puts stderr [list domtree::configure root $root]
00159 set docel [dom::document cget $root -documentElement]
00160 if {[string length $docel]} {
00161 _refresh $path $root
00162 _add_node $path root $root
00163
00164 # Listen for UI events
00165 dom::node addEventListener $docel DOMActivate [namespace code [list _node_selected $path]] -usecapture 1
00166
00167 # Listen for mutation events
00168 dom::node addEventListener $docel DOMSubtreeModified [namespace code [list _node_tree_modified $path]] -usecapture 1
00169 dom::node addEventListener $docel DOMNodeInserted [namespace code [list _node_inserted $path]] -usecapture 1
00170 dom::node addEventListener $docel DOMNodeRemoved [namespace code [list _node_removed $path]] -usecapture 1
00171 dom::node addEventListener $docel DOMCharacterDataModified [namespace code [list _node_data_modified $path]] -usecapture 1
00172 dom::node addEventListener $docel DOMAttrModified [namespace code [list _node_attr_modified $path]] -usecapture 1
00173 dom::node addEventListener $docel DOMAttrRemoved [namespace code [list _node_attr_removed $path]] -usecapture 1
00174 }
00175 }
00176 }
00177
00178 if {!$rn && [Widget::hasChanged $path -showtextcontent showtext]} {
00179 _refresh_all $path root
00180 }
00181
00182 return $res
00183 }
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196 ret domtree::refresh (type path , type node) {
00197 _refresh $path $node
00198 return {}
00199 }
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212 ret domtree::xview (type path , type args) {
00213 eval $path.tree xview $args
00214 }
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227 ret domtree::yview (type path , type args) {
00228 eval $path.tree yview $args
00229 }
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243 ret domtree::find (type path , type findInfo , optional confine ={)} {
00244 set tnode [$path.tree find $findInfo $confine]
00245 return [_tree_to_dom $tnode]
00246 }
00247
00248 # domtree::_dom_to_tree --
00249 #
00250 # Map a DOM node to a tree node
00251 #
00252 # Arguments:
00253 # node DOM node id
00254 #
00255 # Results:
00256 # A string suitable for use as a tree node path
00257
00258 proc domtree::_dom_to_tree node {
00259 if {[catch {dom::document cget $node -documentElement} docel]} {
00260 return $node
00261 } else {
00262 return root
00263 }
00264 }
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276 ret domtree::_tree_to_dom node (
00277 type if , optional [string =compare $node ="root"] , optional
00278 return =$node
00279 , type else , optional
00280 # =Really want =to return =the document =node,
00281 # =but don't =know it =here
00282 return ={
00283 )
00284 }
00285
00286 # domtree::_open_ancestors --
00287 #
00288 # Make sure that all ancestors of the given node are open.
00289 # Don't use "opentree", since that will recursively open
00290 # all children.
00291 #
00292 # Arguments:
00293 # path widget path
00294 # node tree node
00295 #
00296 # Results:
00297 # All ancestors of the node are opened
00298
00299 proc domtree::_open_ancestors {path node} {
00300 while {[string compare $node root]} {
00301
00302 if {[namespace ::Tree [list Widget::getoption $path.tree.$node -open]] != 1} {
00303 namespace ::Tree [list Widget::option = $path.tree.$node -open 1]
00304 uplevel
00305 }
00306 node = [$path.tree parent $node]
00307 }
00308 }
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324 ret domtree::_refresh (type path , type node , type args) {
00325
00326 #puts stderr [list domtree::_refresh $path $node]
00327
00328 switch [set nodetype [::dom::node cget $node -nodeType]] {
00329 document -
00330 documentFragment -
00331 element {
00332 set label [dom::node cget $node -nodeName]
00333 set icon ::domtree::element
00334
00335 if {![string compare $nodetype element]} {
00336
00337 # ID attribute display
00338 if {[Widget::getoption $path -showelementid]} {
00339 array set attributes [array get [::dom::node cget $node -attributes]]
00340 if {[catch {
00341 append label " (id $attributes(id))"
00342 }] && [catch {
00343 append label " (ID $attributes(ID))"
00344 }]} {}
00345 }
00346
00347 if {[Widget::getoption $path -showtextcontent]} {
00348 # Text content display
00349 set temp [_refresh_text_content_display_find_text $node [Widget::getoption $path -showlength]]
00350 if {[string length $temp]} {
00351 append label " \[ [_refresh_string_trim $temp [Widget::getoption $path -showlength]] \]"
00352 }
00353 }
00354
00355 }
00356
00357 if {![string length [dom::node parent $node]]} {
00358 # Root node is special
00359 return {}
00360 }
00361
00362 }
00363 textNode {
00364 array set opts [list -label [dom::node cget $node -nodeValue]]
00365 array set opts $args
00366 set label [_refresh_string_trim [string trim $opts(-label)] [Widget::getoption $path -showlength]]
00367 set icon ::domtree::textNode
00368
00369 # Also do the ancestors
00370 foreach ancestor [lrange [lreplace [::dom::node path $node] end end] 1 end] {
00371 _refresh $path $ancestor
00372 }
00373 }
00374 processingInstruction {
00375 set label [string trim [dom::node cget $node -nodeName]]
00376 set icon ::domtree::PI
00377 }
00378 docType {
00379 set label {}
00380 set icon ::domtree::DocType
00381 }
00382 comment {
00383 set label [_refresh_string_trim [string trim [::dom::node cget $node -nodeValue]] [Widget::getoption $path -showlength]]
00384 set icon ::domtree::Comment
00385 }
00386 entityReference {
00387 set label [::dom::node cget $node -nodeName]
00388 set icon ::domtree::EntityReference
00389 }
00390 default {
00391 set label $nodetype
00392 set icon ::domtree::other
00393 }
00394 }
00395
00396 catch {
00397 $path.tree itemconfigure [_dom_to_tree $node] -image $icon
00398 $path.tree itemconfigure [_dom_to_tree $node] -text $label
00399 }
00400
00401 return {}
00402 }
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417 ret domtree::_refresh_text_content_display_find_text (type node , type len) {
00418 switch -- $len {
00419 0 {
00420 return {}
00421 }
00422 default {
00423 set text {}
00424 foreach child [::dom::node children $node] {
00425 switch [::dom::node cget $child -nodeType] {
00426 document -
00427 documentFragment -
00428 element {
00429 append text \
00430 [_refresh_text_content_display_find_text $child [expr $len - [string length $text]]]
00431 }
00432 textNode {
00433 append text [string range \
00434 [::dom::node cget $child -nodeValue] \
00435 0 [expr $len - [string length $text]] \
00436 ]
00437 }
00438 default {
00439 # Nothing to do
00440 }
00441 }
00442 if {[string length $text] >= $len} {
00443 return $text
00444 }
00445 }
00446
00447 return $text
00448
00449 }
00450 }
00451
00452 return {}
00453 }
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466 ret domtree::_refresh_all (type path , type node) {
00467 foreach child [$path.tree nodes $node] {
00468 _refresh $path [_tree_to_dom $child]
00469 _refresh_all $path $child
00470 }
00471
00472 return {}
00473 }
00474
00475
00476
00477
00478
00479
00480
00481
00482
00483
00484
00485
00486 ret domtree::_refresh_string_trim (type text , type max) {
00487 if {[string length $text] > $max} {
00488 set text [string range $text 0 [expr $max - 3]]...
00489 }
00490 if {[info tclversion] >= 8.1} {
00491 set dot \u2022
00492 } else {
00493 set dot { }
00494 }
00495 regsub -all [format {[%s%s%s%s]+} \n \r { } \t] $text $dot text
00496 return $text
00497 }
00498
00499
00500
00501
00502
00503
00504
00505
00506
00507
00508
00509 ret domtree::_node_selected (type path , type evid) {
00510
00511 set domnode [dom::event cget $evid -target]
00512
00513 # Temporarily remove the -selectcommand callback
00514 # to avoid an infinite loop (continually posting DOM click events)
00515 set cmd [$path.tree cget -selectcommand]
00516 $path.tree configure -selectcommand {}
00517
00518 $path.tree selection set [_dom_to_tree $domnode]
00519
00520 $path.tree configure -selectcommand $cmd
00521
00522 return {}
00523 }
00524
00525
00526
00527
00528
00529
00530
00531
00532
00533
00534 ret domtree::_select_node (type path , type tree , type tnode) {
00535
00536 dom::event postMouseEvent [_tree_to_dom $tnode] click -detail 1
00537
00538 return {}
00539 }
00540
00541
00542
00543
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554 ret domtree::_node_mouse_event (type event , type mod , type path , type tnode) {
00555 variable eventTypeMap
00556
00557 set type $event
00558 catch {set type $eventTypeMap($event)}
00559
00560 set evid [dom::document createEvent [_tree_to_dom $tnode] $type]
00561 dom::event initMouseEvent $evid $type 1 1 {} 0 0 0 0 0 \
00562 [expr {$mod == "Control"}] \
00563 [expr {$mod == "Alt"}] \
00564 [expr {$mod == "Shift"}] \
00565 [expr {$mod == "Meta"}] \
00566 0 {}
00567 dom::node dispatchEvent [_tree_to_dom $tnode] $evid
00568 dom::destroy $evid
00569
00570 # ButtonRelease events also generate DOMActivate events
00571
00572 if {![string compare $event "ButtonRelease"]} {
00573 set detail 1
00574 if {![string compare $mod "Double"]} {
00575 set detail 2
00576 }
00577 dom::event postUIEvent [_tree_to_dom $tnode] DOMActivate -detail $detail
00578 }
00579
00580 return {}
00581 }
00582
00583
00584
00585
00586
00587
00588
00589
00590
00591
00592
00593
00594
00595 ret domtree::_node_ui_event (type event , type path , type tnode) {
00596 variable eventTypeMap
00597
00598 set type $event
00599 catch {set type $eventTypeMap($event)}
00600 dom::event postUIEvent [_tree_to_dom $tnode] $type
00601
00602 return {}
00603 }
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619
00620
00621
00622
00623 ret domtree::_add_node (type path , type tnode , type dnode) {
00624
00625 if {[string compare $tnode "root"]} {
00626
00627 # If the grandparent is open, then do add the items
00628 # This is so that the open/close toggle is added to the parent
00629
00630 set parent [$path.tree parent $tnode]
00631
00632 if {[string compare $parent "root"]} {
00633 set grandParent [$path.tree parent $parent]
00634 if {[string compare $grandParent "root"]} {
00635
00636 # We are beyond the second level from the root
00637
00638 array set ParentInfo [$path.tree itemcget $parent -data]
00639 array set GrandParentInfo [$path.tree itemcget $grandParent -data]
00640 if {[info exists ParentInfo(nodeOpen)]} {
00641 # Parent is open, so add ourself
00642 } elseif {[info exists GrandParentInfo(nodeOpen)]} {
00643 # Grandparent is open, so add ourself
00644 } else {
00645 return {}
00646 }
00647
00648 } else {
00649 # We are two levels down from the root. Go ahead and build,
00650 # but don't automatically mark this node as being open.
00651 }
00652
00653 } else {
00654 # We are a child of the root, so go ahead and build here
00655 _set_client_data $path $tnode nodeOpen 1
00656 }
00657
00658 } else {
00659 # We are the root node, so definitely go ahead and build children
00660 }
00661
00662 foreach child [::dom::node children $dnode] {
00663 # Due to lazy population of the tree, the node may already exist
00664 catch {$path.tree insert end $tnode [_dom_to_tree $child]}
00665 _refresh $path $child
00666
00667 _add_node $path [_dom_to_tree $child] $child
00668 }
00669
00670 return {}
00671 }
00672
00673
00674
00675
00676
00677
00678
00679
00680
00681
00682
00683
00684
00685
00686 ret domtree::_set_client_data (type path , type node , type field , type value) {
00687 array set nodeinfo [$path.tree itemcget $node -data]
00688 set nodeinfo($field) $value
00689 $path.tree itemconfigure $node -data [array get nodeinfo]
00690 }
00691
00692
00693
00694
00695
00696
00697
00698
00699
00700
00701
00702
00703
00704 ret domtree::_unset_client_data (type path , type node , type field) {
00705 array set nodeinfo [$path.tree itemcget $node -data]
00706 catch {unset nodeinfo($field)}
00707 $path.tree itemconfigure $node -data [array get nodeinfo]
00708 }
00709
00710
00711
00712
00713
00714
00715
00716
00717
00718
00719
00720
00721
00722
00723 ret domtree::_node_open (type path , type node) {
00724
00725 _set_client_data $path $node nodeOpen 1
00726 _add_node $path $node [_tree_to_dom $node]
00727
00728 return {}
00729 }
00730
00731
00732
00733
00734
00735
00736
00737
00738
00739
00740
00741
00742
00743
00744
00745
00746
00747
00748 ret domtree::_node_tree_modified (type path , type evid) {
00749
00750 set target [dom::event cget $evid -target]
00751 set children [dom::node children $target]
00752 set branch [Tree::nodes $path.tree [_dom_to_tree $target]]
00753 if {[llength $children] < [llength $branch]} {
00754 for {set idx 0} {$idx < [llength $branch]} {incr idx} {
00755 if {![string length [lindex $children $idx]] || \
00756 [_dom_to_tree [lindex $children $idx]] != [lindex $branch $idx]} {
00757 $path.tree delete [lindex $branch $idx]
00758 break
00759 }
00760 }
00761 }
00762
00763 _refresh $path [dom::event cget $evid -currentNode]
00764
00765 return {}
00766 }
00767
00768
00769
00770
00771
00772
00773
00774
00775
00776
00777
00778
00779 ret domtree::_node_inserted (type path , type evid) {
00780
00781 # Find where the node was inserted into the child list
00782 set newnode [dom::event cget $evid -target]
00783 set parent [dom::node parent $newnode]
00784 set children [dom::node children $parent]
00785 set idx [lsearch $children $newnode]
00786
00787 # Get old tree info
00788 set tparent [_dom_to_tree $parent]
00789 set branch [Tree::nodes $path.tree $tparent]
00790
00791 if {$idx > [llength $branch]} {
00792 # Append the new node to the branch
00793 $path.tree insert end $tparent [_dom_to_tree $newnode]
00794 } else {
00795 # Insert the new node into the branch
00796 $path.tree insert $idx $tparent [_dom_to_tree $newnode]
00797 }
00798
00799 _refresh $path $newnode
00800 _add_node $path [_dom_to_tree $newnode] $newnode
00801
00802 return {}
00803 }
00804
00805
00806
00807
00808
00809
00810
00811
00812
00813
00814
00815
00816 ret domtree::_node_removed (type path , type evid) {
00817
00818 set oldnode [dom::event cget $evid -target]
00819 Tree::delete $path.tree [_dom_to_tree $oldnode]
00820
00821 return {}
00822 }
00823
00824
00825
00826
00827
00828
00829
00830
00831
00832
00833
00834
00835 ret domtree::_node_data_modified (type path , type evid) {
00836 _refresh $path [dom::event cget $evid -target] \
00837 -label [dom::event cget $evid -newValue]
00838 return {}
00839 }
00840
00841
00842
00843
00844
00845
00846
00847
00848
00849
00850
00851
00852 ret domtree::_node_attr_modified (type path , type evid) {
00853 _refresh $path [dom::event cget $evid -target]
00854 return {}
00855 }
00856
00857
00858
00859
00860
00861
00862
00863
00864
00865
00866
00867
00868 ret domtree::_node_attr_removed (type path , type evid) {
00869 _refresh $path [dom::event cget $evid -target]
00870 return {}
00871 }
00872
00873
00874
00875 image create photo ::domtree::element -data {R0lGODlhEAAQANX/AP7
00876 +ef6+d349tv49df18tT289P288718sPz773y7bPw6qzo4qTt5o3o4Ivn4IHb0nTj2XPj2Wrh
00877 107bzzDVxxnQwRecjBCtmRCtmA+AdA6Hew21oQ2NgQq+rQqjlQpoXApmWgm/rQeekAXMuwXG
00878 tQTMu
00879 MS4wAt7tACH5BAEAADMALAAAAAAQABAAAAaRwJlwCBgajZCKBeE4GhuXCCIinQycs+iUA8N4
00880 HgHnZyuTcUoZAyAwIAgA5LKMNGIckksHgiOXoRAnElpUCCB8MlKEGolcGBsiMIwyGCFxHBMd
00881 LpZxZQMPnDJ7fSVConIlKocyJSNChqcjKzEwqyMmQo+0rCYpLyUmwC1CmL/BLBQZIy3KQp7J
00882 yy0KAgUJCwcCQQA7
00883 }
00884 image create photo ::domtree::textNode -data {R0lGODlhEAAOAJH/AP
00885 AAAQAA4AAAI0nIUpxi0AIWoOhAveouPFnHDPhV1CQHmfhFYkmbWMup6p9QbxvbJ3rrNVejuH
00886 4ihjAF+GAgA7
00887 }
00888 image create photo ::domtree::PI -data {R0lGODdhEAAOAPEAALLA3AAAAAAA
00889 DfSEF9dhKIioGFmqR4phFL3eaa6g+6ETaTYsw6IAADs=
00890 }
00891 image create photo ::domtree::DocType -data {R0lGODlhEAAQAKL/APfQ0MmZmYJfX2YAAEoBAf
00892 ACH5BAEAAAUALAAAAAAQABAAAAM7WDKyUjBGB8AaUl4RQFhZNIxM8D2hQJBNgIUKOZ5wsbJu
00893 fcmNfrM1iEoWnIyKqRGqWHoFd0sdEOmAJAAAOw==
00894 }
00895 image create photo ::domtree::Comment -data {R0lGODlhEAAQAKL/AP
00896 ACH5BAEAAAcALAAAAAAQABAAAANDeLrcazBGZ4C917CKTegPBnigwmVDJ5iikWbEelpuV8hi
00897 bhTEMY8vGo+VE8Yeswhv1eDsCkuHb8Qj9KSRo9RniDG3CQA7
00898 }
00899 image create photo ::domtree::EntityReference -data {R0lGODlhEAAQALP/AP7+/vfQ0NOsrMmZmci5uYMwMIJfX2YAAEoBAf
00900 AAAAAAAAACH/C0FET0JFOklSMS4wAt7tACH5BAEAAAkALAAAAAAQABAAAARPMEl5jAlhzJ2O
00901 r1WmbV+CfEdGVtzJTp4xwq90XuvBmZVAeTtbxVAK0nQTYg11mKUGyJJL8ykQOiwAr1nsyDau
00902 mgn3+8xsFwuzeUYopR5NBAA7
00903 }
00904 image create photo ::domtree::other -data {R0lGODlhEAAOAKL/AP
00905 ACH5BAEAAAQALAAAAAAQAA4AAAM4SDSj/m8E0ByrdtI1wI4aFV6ZR5kiJpmrJ6kj+pbuGMCs
00906 fIO1O/MdhmcHeUkCSGJEIriQIByoIwEAOw==
00907 }
00908
00909