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