1# -*- tcl -*- 2 3# $Id: calltree.tcl,v 1.1 2000/06/26 19:23:59 cfelaco Exp $ 4 5# Cbrowser is a C/C++ source code indexing, querying and browsing tool 6# Copyright (C) 1998 B. Christopher Felaco 7 8# This program is free software; you can redistribute it and/or 9# modify it under the terms of the GNU General Public License 10# as published by the Free Software Foundation; either version 2 11# of the License, or (at your option) any later version. 12 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17 18# You should have received a copy of the GNU General Public License 19# along with this program; if not, write to the Free Software 20# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 22# For more information about Cbrowser and it's author see: 23# URL:http://cbrowser.sourceforge.net/ 24# 25# Feel free to contact me at URL:mailto:cfelaco@users.sourceforge.net 26# with enhancements or suggestions. 27 28# $Log: calltree.tcl,v $ 29# Revision 1.1 2000/06/26 19:23:59 cfelaco 30# Initial source file revisions for sourceforge.net. 31# Existing revision history is based on RCS revisions made by original author 32# in private repository. 33# 34# Revision 0.7 1999/03/15 01:20:30 chris 35# Begin support for NT - not complete until pipe asynch IO problems resolved. 36# Use Tk 8.0 style menus. 37# Remove utility functions now in ftcllib. 38# 39# Revision 0.6 1998/10/14 03:37:17 chris 40# Added view file option. 41# Modified edit menu. 42# Improved function definition search algorithm. 43# 44# Revision 0.5 1998/07/16 17:04:00 chris 45# Switched order of tree and edit menu. 46# Added Exit option and removed unusable edit options. 47# Added database info row with query activity light. 48# Added message bar. 49# Fixed calltree_view_node (found wrong locations sometimes). 50# Calltree_expand uses set_callees. 51# 52# Revision 0.4 1998/06/27 22:32:29 chris 53# Initial revision, synched with cbrowser v0.4 54# 55 56#---------------------------------------------------------------------------- 57# This file contains functions that implement the hierarchical function call 58# viewer portions of cbrowser. The functions cannot operate unless called 59# from within cbrowser. 60#---------------------------------------------------------------------------- 61 62############################################################################## 63# 64# Purpose : Create or raise a calltree dialog. 65# 66# Parameters : toplevel - the toplevel dialog 67# 68# Result : NONE 69# 70############################################################################## 71 72proc calltree_dialog {toplevel} { 73 global argv0 query_backend 74 75 if [winfo exists $toplevel] { 76 wm deiconify $toplevel 77 catch {raise $toplevel} 78 return 79 } 80 81 switch -- $query_backend { 82 "xz" { 83 tk_messageBox -type ok -icon info -title "Sorry" \ 84 -message "The calltree is currently unsupported when xz is in use." 85 return 86 } 87 } 88 89 toplevel $toplevel 90 # Withdraw it until it's done 91 wm withdraw $toplevel 92 93 global tcl_version 94 if {$tcl_version < 8.0} { 95 set file_menu $toplevel.file.menu 96 set edit_menu $toplevel.edit.menu 97 set tree_menu $toplevel.tree.menu 98 set help_menu $toplevel.help.menu 99 100 frame $toplevel.menubar -borderwidth 2 -relief raised 101 102 menubutton $toplevel.file -menu $file_menu \ 103 -text "File" -underline 0 104 105 menubutton $toplevel.edit -menu $edit_menu \ 106 -text "Edit" -underline 0 107 108 menubutton $toplevel.tree -menu $tree_menu \ 109 -text "Tree" -underline 0 110 111 menubutton $toplevel.help -menu $help_menu \ 112 -text "Help" -underline 0 113 114 # Pack it up 115 pack $toplevel.file -in $toplevel.menubar -side left 116 pack $toplevel.edit -in $toplevel.menubar -side left 117 pack $toplevel.tree -in $toplevel.menubar -side left 118 pack $toplevel.help -in $toplevel.menubar -side right 119 120 pack $toplevel.menubar -in $toplevel -side top -fill x 121 } else { 122 set file_menu $toplevel.file 123 set edit_menu $toplevel.edit 124 set tree_menu $toplevel.tree 125 set help_menu $toplevel.help 126 127 # Use the new menubar property 128 set menu [menu $toplevel.mainmenu] 129 $menu add cascade -label "File" -underline 0 -menu $file_menu 130 $menu add cascade -label "Edit" -underline 0 -menu $edit_menu 131 $menu add cascade -label "Tree" -underline 0 -menu $tree_menu 132 $menu add separator 133 $menu add cascade -label "Help" -underline 0 -menu $help_menu 134 $toplevel configure -menu $menu 135 } 136 137 # Set up the main File menu 138 menu $file_menu 139 $file_menu add command -label "Select Database..." \ 140 -underline 0 -accel <Meta-s> -command "database_prompt $toplevel" 141 bind $toplevel <Meta-s> "database_prompt $toplevel" 142 $file_menu add command -label "Build Database..." \ 143 -underline 0 -accel <Meta-b> -command "build_dialog .build $toplevel" 144 bind $toplevel <Meta-b> "build_dialog .build $toplevel" 145 $file_menu add command -label "View File..." -underline 0 \ 146 -accel <Meta-v> -command "view_file $toplevel.file_viewer" 147 $file_menu add command -label "Edit File..." -underline 0 \ 148 -accel <Meta-e> -command "edit_selected $toplevel" 149 bind $toplevel <Meta-e> "edit_selected $toplevel" 150 $file_menu add command -label "Query Browser..." -underline 0 \ 151 -accel <Meta-q> -command "wm deiconify .; raise ." 152 bind $toplevel <Meta-q> "wm deiconify .; raise ." 153 $file_menu add separator 154 $file_menu add command -label "Close" -underline 0 \ 155 -command "wm withdraw $toplevel" 156 $file_menu add command -label "Exit" -underline 1 \ 157 -command "quit_cbrowser" 158 159 setup_edit_menu $edit_menu $toplevel.file_viewer 160 161 # However, most entries don't apply 162 $edit_menu entryconfig "Cut" -state disabled 163 #$edit_menu entryconfig "Clear" -state disabled 164 $edit_menu entryconfig "Paste" -state disabled 165 166 # Set up the Tree menu 167 menu $tree_menu 168 $tree_menu add command -label "Set Root..." -underline 4 \ 169 -command "calltree_root_dialog $toplevel" 170 bind $toplevel <Meta-r> "$toplevel.setroot invoke" 171 $tree_menu add command -label "Expand function" -underline 0 \ 172 -command "$toplevel.hier open \[$toplevel.hier curselection\]" 173 $tree_menu add command -label "Collapse function" -underline 0 \ 174 -command "$toplevel.hier close \[$toplevel.hier curselection\]" 175 176 setup_help_menu $help_menu 177 178 # Database info row lists the selected database 179 frame $toplevel.database_row 180 181 label $toplevel.database_label -text "Selected Database: " 182 label $toplevel.database -textvariable database_file -relief groove -padx 3 183 bind $toplevel.database <Button-1> "database_prompt $toplevel" 184 185 global history_button 186 menubutton $toplevel.dbase_recall -image $history_button 187 menu $toplevel.dbase_recall.menu -tearoffcommand "single_tearoff" \ 188 -postcommand "dbase_menu_post $toplevel.dbase_recall.menu" 189 $toplevel.dbase_recall configure -menu $toplevel.dbase_recall.menu 190 191 # Query activity button 192 frame $toplevel.activity -background grey80 \ 193 -width 10 -height 10 -bd 2 -relief sunken ;# -cursor crosshair 194 195 # Pack the database row 196 pack $toplevel.database_label -in $toplevel.database_row -side left 197 pack $toplevel.database -in $toplevel.database_row -side left 198 pack $toplevel.dbase_recall -in $toplevel.database_row -side left -padx 5 199 pack $toplevel.activity -in $toplevel.database_row -side right -padx 5 200 201 pack $toplevel.database_row -side top -fill x -padx 2 -pady 2 202 203 frame $toplevel.pane -relief groove 204 frame $toplevel.pane.left 205 frame $toplevel.pane.right 206 207 # Create the paned window 208 Pane_Create $toplevel.pane.left $toplevel.pane.right -orient horizontal \ 209 -in $toplevel.pane -percent 0.20 -minpercent 0.10 210 211 hierarchy $toplevel.hier \ 212 -browsecmd calltree_expand \ 213 -nodelook calltree_look \ 214 -command calltree_view_node \ 215 -root main ;#-common 1 216 217 # When a node is selected with a single click, display the file 218 bind $toplevel.hier <Button-1> "+calltree_click %W %x %y" 219 220 pack $toplevel.hier -in $toplevel.pane.left -padx 4 -expand yes -fill both 221 222 frame $toplevel.file_frame 223 224 # Create a row for the file and line information 225 frame $toplevel.info_row 226 label $toplevel.file_label -text "File: " 227 entry $toplevel.file_field -state disabled \ 228 -textvariable current_file($toplevel.file_viewer) 229 label $toplevel.line_label -text "Line: " 230 entry $toplevel.line_field -state disabled -width 4 \ 231 -textvariable current_line($toplevel.file_viewer) 232 233 pack $toplevel.file_label -in $toplevel.info_row -side left 234 pack $toplevel.file_field -in $toplevel.info_row -side left -fill x -expand yes 235 pack $toplevel.line_label $toplevel.line_field -in $toplevel.info_row \ 236 -side left 237 pack $toplevel.info_row -in $toplevel.file_frame -in $toplevel.pane.right \ 238 -side top -fill x -padx 4 -pady 4 239 240 # Create the file_viewer and pack it below the information row 241 setup_file_viewer $toplevel $toplevel.file_frame 242 243 # Add the convenience of clicking on a function to select 244 $toplevel.file_viewer tag bind matched_text <Button-1> \ 245 [list calltree_hyper $toplevel.hier %W %x %y] 246 247 pack $toplevel.file_frame -in $toplevel.pane.right \ 248 -side top -expand yes -fill both -padx 4 249 # The 4 pixels of padding allow space for the grip 250 251 pack $toplevel.pane -in $toplevel -side top -expand yes -fill both 252 253 # Message bar 254 message $toplevel.message -textvariable status_msg -relief sunken 255 bind $toplevel.message <Configure> "%W configure -width %w" 256 # To clear the message, click on it 257 bind $toplevel.message <Button-1> {set_message ""} 258 259 pack $toplevel.message -in $toplevel -side bottom -fill x -padx 2 260 261 # Convenience keys 262 bind $toplevel <<Help>> "help_proc calltree" 263 264 # Clear the cache when the window is destroyed 265 wm protocol $toplevel WM_DELETE_WINDOW "calltree_destroy $toplevel" 266 267 wm geometry $toplevel 800x600 268 wm deiconify $toplevel 269 wm title $toplevel "[wm title .] calltree" 270 catch {raise $toplevel} 271 tkwait visibility $toplevel 272 273 calltree_select $toplevel.hier main 274} 275 276############################################################################## 277# 278# Purpose : Return the display parameters of a hierarchy node. 279# 280# Parameters : see -nodelook flag of hierarchy command 281# 282# Result : see -nodelook flag of hierarchy command 283# 284############################################################################## 285 286proc calltree_look {widget nodepath isopen} { 287 global node_open node_closed 288 if {$isopen} { 289 set image $node_open 290 set color DarkGreen 291 } else { 292 set image $node_closed 293 set color DarkBlue 294 } 295 return [list [lindex $nodepath end] "" $image $color] 296} 297 298############################################################################## 299# 300# Purpose : Respond to a single button click in the calltree by calling 301# calltree_view_node. 302# 303# Parameters : widget - the widget clicked in 304# x,y - the coordinates of the click 305# 306# Result : NONE 307# 308############################################################################## 309 310proc calltree_click {widget x y} { 311 set nodepath [lindex [$widget get @$x,$y] 0] 312 if {[string length $nodepath] > 0} { 313 calltree_view_node $widget $nodepath 314 } 315} 316 317############################################################################## 318# 319# Purpose : Select a node in the calltree hierarchy and view it's node. 320# 321# Parameters : widget - the widget clicked in 322# nodepath - the path to the selected node 323# 324# Result : NONE 325# 326############################################################################## 327 328proc calltree_select {widget nodepath} { 329 $widget select clear 330 $widget select set $nodepath 331 $widget see $nodepath 332 333 calltree_view_node $widget $nodepath 334} 335 336############################################################################## 337# 338# Purpose : Act upon the selection of a calltree node. 339# Display the function in the file_viewer. 340# 341# Parameters : widget - the hierarchy widget 342# nodepath - the path to the selected node 343# 344# Result : NONE 345# 346############################################################################## 347 348proc calltree_view_node {widget nodepath {showing {}}} { 349 global function_loc 350 351 set root [winfo toplevel $widget] 352 set_root_base $root 353 354 set function [lindex $nodepath end] 355 356 if [info exists function_loc($function)] { 357 if {[llength $function_loc($function)] == 2} { 358 set foundfile [lindex $function_loc($function) 0] 359 set foundline [lindex $function_loc($function) 1] 360 } else { 361 # If the location of the function is out of scope, cleanup and quit 362 display_clear $root 363 return 364 } 365 } else { 366 367 # Start the query activity indicator 368 activity_start 500 $base.activity 369 370 # Call query_execute, but make sure to handle any errors and reraise later. 371 # (I wish Tcl was more like lisp sometimes) 372 set failed 0 373 if [catch {query_execute Symbol $function} results] { 374 global errorInfo errorCode 375 376 activity_finish 500 $base.activity 377 378 tk_messageBox -parent $widget -type ok -title Error -message $results 379 return 380 } 381 382 # Assume for now that the first entry is always the main function 383 foreach line $results { 384 foreach {infile infunc inline codeline} $line {} 385 386 if {$infunc == "(null)" || 387 $infunc == "<global>" || 388 $infunc == $function} { 389 390 # If the code contains the function followed by parentheses, 391 # this is probably it. 392 # If the function returns a pointer to function, the declaration could 393 # get hairy. Anything more complicated than this will not be 394 # attempted. 395 if {[regexp -- "$function\[ \t\]*\\(" $codeline] || 396 [regexp -- "\\(\[^()\]*$function\[^()a-zA-Z0-9_\]*\\)\[ \t\]*\\(" \ 397 $codeline]} { 398 set foundfile $infile 399 set foundline $inline 400 401 # If there's a semicolon, or the file is a header, it's probably a 402 # declaration, so keep looking, but keep this as a guess in case 403 # nothing else is found. 404 if {![string match {*.[hH]} $infile] && 405 [string first ";" $codeline] < 0} { 406 break 407 } 408 } 409 } 410 } 411 412 activity_finish 500 $base.activity 413 414 if [info exists foundfile] { 415 set function_loc($function) "$foundfile $foundline" 416 } { 417 display_clear $base 418 419 # Store a null value for this function to skip future attempts 420 set function_loc($function) "" 421 return 422 } 423 } 424 425 set_message "" 426 427 global called_by 428 if [info exists called_by($function)] { 429 set highlights $called_by($function) 430 } else { 431 set highlights "" 432 } 433 434 display_file $root $foundfile $foundline $highlights 435 436 focus $widget 437} 438 439############################################################################## 440# 441# Purpose : Respond to button clicks on function names in the calltree 442# file browser by selecting the corresponding function. 443# 444# Parameters : treew - the hierarchy widget 445# textw - the text widget clicked in 446# x,y - the coordinates of the pointer 447# 448# Result : see -browsecmd flag of hierarchy command 449# 450############################################################################## 451 452proc calltree_hyper {treew textw x y} { 453 454 set nodepath [lindex [$treew get [$treew curselection]] 0] 455 456 set index [$textw index @$x,$y] 457 set range [$textw tag prevrange matched_text $index] 458 set function [$textw get [lindex $range 0] [lindex $range 1]] 459 460 set newpath [concat $nodepath $function] 461 calltree_select $treew [concat $nodepath $function] 462} 463 464############################################################################## 465# 466# Purpose : Act upon the expansion of a node of a calltree hierarchy. 467# Return a list of functions called by the given function. 468# 469# Parameters : see -browsecmd flag of hierarchy command 470# 471# Result : see -browsecmd flag of hierarchy command 472# 473############################################################################## 474 475proc calltree_expand {widget nodepath} { 476 global query_backend database_file called_by 477 478 set root [winfo toplevel $widget] 479 set_root_base $root 480 481 set function [lindex $nodepath end] 482 483 # Check the cache for the functions called by this function 484 if ![info exists called_by($function)] { 485 486 # Start the query activity indicator 487 activity_start 500 $base.activity 488 489 # Call query_execute, but make sure to handle any errors and reraise later. 490 set failed 0 491 if [catch {query_execute CalledBy $function} results] { 492 global errorInfo errorCode 493 494 tk_messageBox -parent $widget -type ok -title Error -message $results 495 496 activity_finish 500 $base.activity 497 498 return "" 499 } 500 501 # Set the callee and line number lists 502 set_callees $function $results 503 504 set numcalls [expr [llength $called_by($function)] / 2] 505 set_message "Function $function makes $numcalls function calls" 506 } 507 508 # Extract the function names from the called_by structure 509 foreach {func linenum} $called_by($function) { 510 set dummy($func) "" 511 } 512 513 activity_finish 500 $base.activity 514 515 return [lsort [array names dummy]] 516} 517 518############################################################################## 519# 520# Purpose : Open a dialog to prompt for the new root to the calltree. 521# 522# Parameters : base - the root of the calltree dialog 523# 524# Result : NONE 525# 526############################################################################## 527 528proc calltree_root_dialog {base} { 529 530 set toplevel $base.root 531 532 if { ![winfo exists $toplevel] } { 533 toplevel $toplevel 534 wm title $toplevel "Set Root" 535 536 frame $toplevel.toprow 537 pack $toplevel.toprow -side top -expand yes -fill x -pady 5 538 539 label $toplevel.l -text "New Root:" 540 entry $toplevel.entry 541 542 pack $toplevel.l -side left -in $toplevel.toprow -padx 5 543 pack $toplevel.entry -side left -in $toplevel.toprow -expand yes -fill x 544 545 frame $toplevel.buttonbar 546 pack $toplevel.buttonbar -side top -expand yes -pady 3 547 548 button $toplevel.ok -text "OK" \ 549 -command "calltree_set_root $base \[$toplevel.entry get\] 550 wm withdraw $toplevel" 551 button $toplevel.cancel -text "Cancel" \ 552 -command "wm withdraw $toplevel" 553 global button_defaults 554 if {$button_defaults} { 555 $toplevel.ok configure -default active 556 $toplevel.cancel configure -default normal 557 } 558 559 pack $toplevel.ok $toplevel.cancel \ 560 -in $toplevel.buttonbar -side left -padx 10 561 562 bind $toplevel <Return> "$toplevel.ok invoke" 563 bind $toplevel <Escape> "$toplevel.cancel invoke" 564 565 tkwait visibility $toplevel 566 } else { 567 wm deiconify $toplevel 568 } 569 catch {raise $toplevel} 570 focus -force $toplevel.entry 571} 572 573############################################################################## 574# 575# Purpose : Set the root node of the calltree 576# 577# Parameters : toplevel - the calltree toplevel window 578# function - the new root function 579# 580# Result : NONE 581# 582############################################################################## 583 584proc calltree_set_root {toplevel function} { 585 $toplevel.hier configure -root $function 586 calltree_select $toplevel.hier $function 587} 588 589############################################################################## 590# 591# Purpose : Clean up when the calltree dialog is destroyed. 592# 593# Parameters : toplevel - the dialog box being destroyed 594# 595# Result : NONE 596# 597############################################################################## 598 599proc calltree_destroy {toplevel} { 600 global called_by function_loc 601 602 catch {unset called_by}; catch {unset function_loc} 603 604 global current_file current_line 605 set current_file($toplevel.file_viewer) "" 606 set current_line($toplevel.file_viewer) "" 607 608 destroy $toplevel 609} 610 611set node_closed [image create photo] 612$node_closed put { 613"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 614"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 615"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 616"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 617"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9" 618"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #ffffff #d9d9d9 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9" 619"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9" 620"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 621"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 622"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 623"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 624} 625 626set node_open [image create photo] 627$node_open put { 628"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 629"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 630"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 631"#d9d9d9 #000000 #000000 #000000 #000000 #000000 #000000 #000000 #000000 #000000 #000000 #000000" 632"#d9d9d9 #d9d9d9 #000000 #d9d9d9 #ffffff #d9d9d9 #ffffff #d9d9d9 #ffffff #d9d9d9 #000000 #d9d9d9" 633"#d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #ffffff #d9d9d9 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9" 634"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #ffffff #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9" 635"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 636"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #000000 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 637"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 638"#d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9 #d9d9d9" 639} 640