home *** CD-ROM | disk | FTP | other *** search
/ PCNET 2006 September - Disc 1 / PCNET_CD_2006_09.iso / linux / puppy-barebones-2.01r2.iso / pup_201.sfs / usr / lib / ml / ml.tcl < prev    next >
Encoding:
Text File  |  2004-05-08  |  48.0 KB  |  1,675 lines

  1.  
  2. #====================================================================#
  3. #     Editor written in Tcl/Tk for editing TCL source & projects     #
  4. #        (c) Peter Campbell Software; 28-04-2000          #
  5. #====================================================================#
  6.  
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. # GNU General Public License for more details.
  16.  
  17. # some features
  18. # ==========
  19. # basic tcl syntax highlighting.
  20. # procedure window, select a procedure to go directly to it.
  21. # right click on a word to have the word "copied" to the "find" window
  22. # multiple windows open simultaneously
  23. # the editor can be invoked with file names on the command line, including wildcards (don't do too many)
  24. # the replace function
  25. # undo/redo
  26. # brace matching - highlight matching braces when cursor is on a brace (also quotes & square brackets)
  27. # goto line number (control-g or "view" menu)
  28. # added "font larger/smaller" to the view menu; 20th June 02 (v1.09)
  29. # changed window system so only opens 1 toplevel window, uses frames & packing for window/file selection
  30.  
  31. # added a splash screen on startup to show "loading file ..." (v1.10)
  32. # added a "search - grep" function
  33. # don't syntax highlight files at startup, do when they are first viewed
  34. # the most recent find/replace strings weren't being stored at the start of the find/replace history
  35.  
  36. # todo list
  37. # ======
  38. # reverse searching
  39.  
  40. # URL = http://fastbase.co.nz/edit/index.html
  41.  
  42. #====================================================================#
  43.  
  44. # this program uses a global array editor() to store editor information
  45. # editor(window_number,window) = frame/window
  46. # editor(window_number,file)     = file name
  47. # editor(window_number,status)     = "" or "modified" (or "READ ONLY")
  48. # editor(window_number,procs)   = list of procedure names
  49.  
  50. proc centre_window { w } {
  51.     after idle "
  52.         update idletasks
  53.  
  54.         # centre
  55.         set xmax \[winfo screenwidth $w\]
  56.         set ymax \[winfo screenheight $w\]
  57.         set x \[expr \{(\$xmax - \[winfo reqwidth $w\]) / 2\}\]
  58.         set y \[expr \{(\$ymax - \[winfo reqheight $w\]) / 2\}\]
  59.  
  60.         wm geometry $w \"+\$x+\$y\""
  61. }
  62.  
  63. # to start things rolling display a "splash screen"
  64. # see "Effective Tcl/Tk Programming" book, page 254-247 for reference
  65. wm withdraw .
  66. toplevel .splash -borderwidth 4 -relief raised
  67. wm overrideredirect .splash 1
  68.  
  69. centre_window .splash
  70.  
  71. # BK Arial 9 is too small, make it 10...
  72. label .splash.info -text "http://www.fastbase.co.nz/edit/index.html" -font {Arial 10}
  73. pack .splash.info -side bottom -fill x
  74.  
  75. label .splash.title -text "-- ML Editor Tcl/Tk --" -font {Arial 18 bold} -fg blue
  76. pack .splash.title -fill x -padx 8 -pady 8
  77.  
  78. # BK ditto...
  79. set splash_status "Loading configuration file ..."
  80. label .splash.status -textvariable splash_status -font {Arial 10} -width 50 -fg darkred
  81. pack .splash.status -fill x -pady 8
  82.  
  83. update
  84.  
  85. # note: change this to correct path (should really use "package require" syntax).
  86. if {[catch "source combobox.tcl"]} {
  87.     source /fbase/edit/combobox.tcl
  88. }
  89.  
  90. if {[catch "source supertext.tcl"]} {
  91.     source /fbase/edit/supertext.tcl
  92. }
  93.  
  94. # == miscellaneous =================================================#
  95.  
  96. # temporary procedure for logging debug messages
  97. proc log {message} {
  98.     set fid [open "ml.log" a+]
  99.     set time [clock format [clock seconds] -format "%d-%m-%Y %I:%M:%S %p"]
  100.     puts $fid "$time  $message"
  101.     close $fid
  102. }
  103.  
  104. #== syntax highlight ================================================#
  105.  
  106. proc tag_word {editor_no word t line_no startx x {tag_name ""}} {
  107.     global editor
  108.     global syntax
  109.     set ext $editor($editor_no,extension)
  110.  
  111.     if {$tag_name != ""} {
  112.         $t tag add $tag_name $line_no.$startx $line_no.$x
  113.     } elseif {[array names syntax $ext,$word] != ""} {
  114.         $t tag add command $line_no.$startx $line_no.$x
  115.     } elseif {[string is double -strict $word]} {
  116.         $t tag add number $line_no.$startx $line_no.$x
  117.     } elseif {[string range $word 0 0] == "$"} {
  118.         $t tag add variable $line_no.$startx $line_no.$x
  119.     }
  120. }
  121.  
  122. proc syntax_highlight { editor_no start_line end_line } {
  123.     global editor
  124.  
  125.     set t $editor($editor_no,text)
  126.  
  127.     if {$end_line == "end"} {
  128.         set end $end_line
  129.     } else {
  130.         set end $end_line.end
  131.     }
  132.  
  133.     # remove all existing tags from the text (excluding the proc tag)
  134.     foreach tag {command comment string number variable} {
  135.         $t tag remove $tag $start_line.0 $end
  136.     }
  137.  
  138.     set line_no $start_line
  139.     set next_no [expr {$start_line + 1}]
  140.  
  141.     if {$end_line == "end"} {
  142.         set proc_no 0
  143.         set editor($editor_no,procs) ""
  144.     } else {
  145.         set proc_no $editor($editor_no,proc_no)
  146.     }
  147.  
  148.     while {[set line [$t get $line_no.0 $next_no.0]] != "" && $line_no <= $end_line} {
  149.         # replace all tabs with spaces for consistency/simpler comparisons
  150.         regsub -all "\t" $line " " line
  151.  
  152.         set trimmed [string trim $line]
  153.         set we [string wordend $trimmed 0]
  154.         set first_word [string range $trimmed 0 [expr {$we - 1}]]
  155.  
  156.         if {[string range $trimmed 0 0] == "#"} {
  157.             # comment line, simply colour the whole line
  158.             $t tag add comment $line_no.0 $line_no.end
  159.         } elseif {$first_word == "proc"} {
  160.             # proc statement, colour the whole line and add the proc name to the proc list
  161.             set end [string first " " $trimmed [expr {$we + 1}]]
  162.             if {$end == -1} {
  163.                 # provide some extra handling for procedure names ending with semi-colon
  164.                 # this to support some other languages besides tcl
  165.                 set end [string first ";" $trimmed [expr {$we + 1}]]
  166.             }
  167.  
  168.             set proc_name [string trim [string range $trimmed [expr {$we + 1}] $end]]
  169.             if {$proc_name != ""} {
  170.                 set exists 0
  171.                 foreach procs $editor($editor_no,procs) {
  172.                     if {[lindex $procs 0] == $proc_name} {
  173.                         set exists 1
  174.                         break
  175.                     }
  176.                 }
  177.                 if {!$exists} {
  178.                     incr proc_no
  179.                     $t mark set mark_$proc_no $line_no.0
  180.                     lappend editor($editor_no,procs) [list $proc_name $proc_no]
  181.                     $t tag add proc $line_no.0 $line_no.end
  182.                 }
  183.             }
  184.         } else {
  185.             # general line, review all words within the line and colourise appropriately
  186.             set startx 0
  187.             set word ""
  188.             set length [string length $line]
  189.             set quote 0
  190.  
  191.             for {set x 0} {$x < $length} {incr x} {
  192.                 set c [string range $line $x $x]
  193.                 if {$quote != 0} {
  194.                     if {$c == $quote} {
  195.                         tag_word $editor_no $word $t $line_no $startx [expr {$x + 1}] "string"
  196.                         set quote 0
  197.                         set word ""
  198.                     }
  199.                 } elseif {[string first $c "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.$:"] != -1} {
  200.                     if {$word == ""} { set startx $x }
  201.                     append word $c
  202.                 } elseif {$word != ""} {
  203.                     tag_word $editor_no $word $t $line_no $startx $x
  204.                     set word ""
  205.                 } elseif {$c == "\"" || $c == "'"} {
  206.                     set startx $x
  207.                     set quote $c
  208.                 }
  209.                 if {$c == "\\"} { incr x }
  210.             }
  211.  
  212.             if {$word != ""} {
  213.                 tag_word $editor_no $word $t $line_no $startx $x
  214.             }
  215.         }
  216.  
  217.         incr line_no
  218.         incr next_no
  219.     }
  220.  
  221.     # store the most recent procedure number (proc_no)
  222.     set editor($editor_no,proc_no) $proc_no
  223.  
  224.     # set "syntax" flag
  225.     set editor($editor_no,syntax) 1
  226. }
  227.  
  228. #== double-click on braces to select text ==================================#
  229.  
  230. proc selectClosingBrace {widget} {
  231.     if {[string equal [$widget get insert-1chars] \\ ] } {
  232.     return 0
  233.     }
  234.     set mark [$widget index insert]
  235.     set openingChar [$widget get $mark] 
  236.     switch $openingChar \{ {
  237.     set closingChar \}
  238.     } \" {
  239.     set closingChar \"
  240.     } \[ {
  241.     set closingChar \]
  242.     } default {
  243.     return 0
  244.     }
  245.     set target [$widget index $mark+1chars]
  246.     while {![info complete [$widget get $mark $target+1chars]]} {
  247.     set target [$widget search $closingChar $target+1chars end]
  248.     if {$target == ""} {
  249.         return 0
  250.     }
  251.     }
  252.     $widget tag add sel $mark $target+1chars
  253.     return 1
  254. }
  255.  
  256. #== validate procedures =============================================#
  257.  
  258. # this procedure hasn't been tested to work yet
  259. # the "delete" event needs to be modified to remove all marks within the deleted text
  260. # see "proc $t" the overriding text widget procedure
  261.  
  262. proc validate_procedures { editor_no } {
  263.     global editor
  264.     set t $editor($editor_no,text)
  265.  
  266.     # check each procedure mark still exists, if not then delete the procedure name
  267.     set index 0
  268.     foreach procs $editor($editor_no,procs) {
  269.         set no [lindex $procs 1]
  270.         if {[$t index mark_$no] == ""} {
  271.             set editor($editor_no,procs) [lreplace $editor($editor_no,procs) $index $index]
  272.         }
  273.         incr index
  274.     }
  275. }
  276.  
  277. #== update'status ===================================================#
  278.  
  279. # this procedure updates the right hand panel which includes the file/directory, status and procedures
  280. # this procedure is normally called after every key/button release to update the cursor position
  281.  
  282. proc update_status { editor_no } {
  283.     global editor
  284.  
  285.     set sw $editor($editor_no,status_window)
  286.     set t $editor($editor_no,text)
  287.  
  288.     $sw configure -state normal
  289.     $sw delete 1.0 end
  290.  
  291.     $sw insert end "File:\t$editor($editor_no,title)\n"
  292.     $sw insert end "Dir:\t[file dirname $editor($editor_no,file)]\n"
  293.  
  294.     $sw insert end "Editor:\tVersion $editor(version)\n"
  295.     $sw insert end "Status:\t$editor($editor_no,status)\n"
  296.  
  297.     $sw insert end "Position:\t[$t index insert]\nFont:\t[$t cget -font]\n\n"
  298.  
  299.     foreach procs [lsort -index 0 $editor($editor_no,procs)] {
  300.         set proc [lindex $procs 0]
  301.         set no [lindex $procs 1]
  302.         set original_bg [$sw cget -background]
  303.         $sw tag bind proc_$no <Any-Enter> "$sw tag configure proc_$no -background skyblue1"
  304.         $sw tag bind proc_$no <Any-Leave> "$sw tag configure proc_$no -background $original_bg"
  305.         $sw tag bind proc_$no <1> "$t mark set insert mark_$no;$t see insert;update_status $editor_no"
  306.         $sw insert end "$proc\n" proc_$no
  307.     }
  308.  
  309.     $sw configure -state disabled
  310. }
  311.  
  312. #== dynamic window menu option for selecting any active editor window ============================#
  313.  
  314. proc make_window_active { editor_no } {
  315.     global editor
  316.  
  317.     # find the current window and remove it from the screen
  318.     set current $editor(current)
  319.  
  320.     # same file? do nothing (return)
  321.     if {$current == $editor_no} { return }
  322.  
  323.     if {$current != ""} {
  324.         set w $editor($current,window)
  325.         pack forget $w
  326.         destroy .menu
  327.     }
  328.  
  329.     # get the text widget window
  330.     set t $editor($editor_no,text)
  331.  
  332.     # the title of the window is "filename" (excluding drive/directory)
  333.     wm title . $editor($editor_no,title)
  334.  
  335.     # create the main window menus
  336.     menu .menu -tearoff 0
  337.  
  338.     # add the "file" menu
  339.     set m .menu.file
  340.     menu $m -tearoff 0
  341.     .menu add cascade -label "File" -menu $m -underline 0
  342.     $m add command -label "New" -command make_editor -underline 0
  343.     $m add command -label "Open" -command "open_file $editor_no" -underline 0
  344.     $m add command -label "Save" -command "save_file $editor_no" -underline 0 -accelerator Ctrl+S
  345.     $m add command -label "Save As" -command "save_file_as $editor_no" -underline 5
  346.     # windows? include the "Print" option
  347.     if {$::tcl_platform(platform) == "windows"} {
  348.         $m add command -label "Print" -command "print_file $editor_no" -underline 0 -accelerator Ctrl+P
  349.     }
  350.  
  351.     # all windows have the close and exit function
  352.     # the close window function closes the window (unless the main window, then clears the window)
  353.     # the exit function closes all windows then exits the application
  354.     $m add separator
  355.     $m add command -label "Close Window" -underline 0 -command "close_window $editor_no"
  356.     $m add separator
  357.     $m add command -label "Exit ML EDITOR" -underline 1 -command "exit_editor"
  358.  
  359.     # add the "edit" menu
  360.     set m .menu.edit
  361.     menu $m -tearoff 0
  362.     .menu add cascade -label "Edit" -menu $m -underline 0
  363.     $m add command -label "Undo" -command "$t undo" -underline 0 -accelerator Ctrl+Z
  364.     $m add separator
  365.     $m add command -label "Cut" -command "tk_textCut $t" -underline 0 -accelerator Ctrl+X
  366.     $m add command -label "Copy" -command "tk_textCopy $t" -underline 0 -accelerator Ctrl+C
  367.     $m add command -label "Paste" -command "tk_textPaste $t" -underline 0 -accelerator Ctrl+V
  368.  
  369.     # add the "view" menu
  370.     set m .menu.view
  371.     menu $m -tearoff 0
  372.     .menu add cascade -label "View" -menu $m -underline 0
  373.     $m add check -label "Goto Line" -command "goto_line $editor_no" -underline 0
  374.     $m add check -label "Word Wrap" -command "toggle_word_wrap $editor_no" \
  375.         -underline 0 -variable editor($editor_no,wordwrap) -onvalue 1 -offvalue 0
  376.     $m add separator
  377.     $m add command -label "Refresh Highlighting" -command "syntax_highlight $editor_no 1 end" -underline 0
  378.     $m add separator
  379.     $m add command -label "Font Larger" -command "view_font_size $editor_no 1" -underline 5 -accelerator Ctrl+Plus
  380.     $m add command -label "Font Smaller" -command "view_font_size $editor_no -1" -underline 5 -accelerator Ctrl+Minus
  381.  
  382.     # add the "Search" menu
  383.     set m .menu.search
  384.     menu $m -tearoff 0
  385.     .menu add cascade -label "Search" -menu $m -underline 0
  386.     # the following commands are duplicated below, see the keyboard/accelerator bindings
  387.     $m add command -label "Find ..." -accelerator Ctrl+F -command "search_find $editor_no" -underline 0
  388.     $m add command -label "Find Next" -accelerator "F3" -command "search_find_next $editor_no" -underline 0
  389.     $m add command -label "Replace ..." -accelerator Ctrl+G -command "search_replace $editor_no" -underline 0
  390.     $m add separator
  391.     $m add command -label "Grep ..." -command "grep_search $editor_no" -underline 0
  392.  
  393.     # create the "window" menu option
  394.     set m .menu.window
  395.     menu $m -tearoff 0 -postcommand "create_window_menu $m"
  396.     .menu add cascade -label "Window" -menu $m -underline 0
  397.  
  398.     # create the "window" menu option
  399.     set m .menu.help
  400.     menu $m -tearoff 0
  401.     .menu add cascade -label "Help" -menu $m -underline 0
  402.     $m add command -label "About ML ..." -command about_window -underline 0
  403.  
  404.     . configure -menu .menu
  405.  
  406.     # display the selected window on the screen
  407.     set w $editor($editor_no,window)
  408.     pack $w -expand yes -fill both
  409.  
  410.     # store the current editor number
  411.     set editor(current) $editor_no
  412.  
  413.     # has window been opened with syntax highlighting?
  414.     if {!$editor($editor_no,syntax)} {
  415.         syntax_highlight $editor_no 1 end
  416.     }
  417.  
  418.     # focus on the text widget
  419.     focus -force $t
  420.  
  421.     update_status $editor_no
  422. }
  423.  
  424. # dynamically create the "window" menu with a list of all open files
  425.  
  426. proc about_window {} {
  427.     global editor
  428.  
  429.     set w .about
  430.  
  431.     # destroy the find window if it already exists
  432.     if {[winfo exists $w]} { destroy $w }
  433.  
  434.     # create the new "find" window
  435.     toplevel $w
  436.     wm transient $w .
  437.     wm title $w "About - ML Editor"
  438.  
  439.     label $w.1 -text "ML Text Editor v$editor(version)" -font {Arial 18 bold} -fg blue
  440.     label $w.2 -text "ML was written by Peter Campbell, pc@acs.co.nz\nWeb Site: http://www.fastbase.co.nz/edit/index.html" -font {Arial 11} -fg darkblue
  441.     label $w.3 -text "Additional credit to Bryan Oakley for combobox.tcl & supertext.tcl (see source)" -font {Arial 10} -fg darkred
  442.     label $w.4 -text "If you have any questions about this software please\nread the source code first and see the web site, then feel free to email me." -font {Arial 9}
  443.  
  444.     button $w.b -text "Close" -command "destroy $w"
  445.  
  446.     pack $w.1 $w.2 $w.3 $w.4 $w.b -pady 5
  447.     focus -force $w.b
  448.  
  449.     centre_window $w
  450. }
  451.  
  452. proc create_window_menu { m } {
  453.     global editor
  454.  
  455.     # remove all existing options
  456.     $m delete 0 end
  457.  
  458.     # starting menu item (1, 2, 3 ... A, B, C ...)
  459.     set number 1
  460.  
  461.     foreach name [lsort -dictionary [array names editor *,file]] {
  462.         set no [lindex [split $name ","] 0]
  463.         if {$editor($no,status) != "CLOSED"} {
  464.             if {$number < 10} {
  465.                 set item $number
  466.             } else {
  467.                 set item [format "%2X" [expr {$number + 55}]]
  468.                 eval "set item \\\x$item"
  469.             }
  470.             if {$item <= "Z"} {
  471.                 $m add check -label "$item. $editor($no,title)" -command "make_window_active $no" \
  472.                     -underline 0 -variable editor($no,status) -onvalue $editor($no,status) -offvalue $editor($no,status) \
  473.                     -indicatoron [expr {$editor($no,status) == "MODIFIED"}]
  474.             } else {
  475.                 $m add check -label "$editor($no,title)" -command "make_window_active $no" \
  476.                     -variable editor($no,status) -onvalue $editor($no,status) -offvalue $editor($no,status) \
  477.                     -indicatoron [expr {$editor($no,status) == "MODIFIED"}]
  478.             }
  479.             incr number
  480.         }
  481.     }
  482. }
  483.  
  484. #== search_find =====================================================#
  485.  
  486. proc search_find { editor_no } {
  487.     global editor
  488.  
  489.     set w .find
  490.  
  491.     # destroy the find window if it already exists
  492.     if {[winfo exists $w]} { destroy $w }
  493.  
  494.     # create the new "find" window
  495.     toplevel $w
  496.     wm transient $w .
  497.     wm title $w "Find"
  498.  
  499.     set f1 [frame $w.f1]
  500.     set f2 [frame $w.f2]
  501.  
  502.     set ft [frame $f1.top]
  503.     label $ft.text -text "Find What?"
  504.     set entry [combobox::combobox $ft.find -width 30 -value [lindex $editor(find_history) 0]]
  505.     pack $ft.text -side left -anchor nw -padx 4 -pady 4
  506.     pack $entry -side left -anchor nw -padx 4 -pady 4
  507.     pack $ft -side top -anchor nw
  508.  
  509.     checkbutton $f1.case -text "Match Case?" -variable editor(match_case)
  510.     pack $f1.case -side left -padx 4 -pady 4
  511.  
  512.     # populate the combobox with the editor find history
  513.     foreach string $editor(find_history) {
  514.         $entry list insert end $string
  515.     }
  516.  
  517.     button $f2.find -text "Find Next" -command "search_find_command $editor_no $w $entry" -width 10
  518.     button $f2.cancel -text "Cancel" -command "destroy $w" -width 10
  519.     pack $f2.find -side top -padx 8 -pady 4
  520.     pack $f2.cancel -side top -padx 8 -pady 4
  521.  
  522.     pack $f1 -side left -anchor nw
  523.     pack $f2 -side left -anchor nw
  524.  
  525.     bind $entry.entry <Return> "+search_find_command $editor_no $w $entry"
  526.     bind $entry.entry <Escape> "destroy $w"
  527.  
  528.     focus -force $entry
  529.     centre_window $w
  530. }
  531.  
  532. proc search_find_command { editor_no w entry } {
  533.     global editor
  534.     set editor(find_string) [$entry get]
  535.     destroy $w
  536.  
  537.     # null string? do nothing
  538.     if {$editor(find_string) == ""} {
  539.         return
  540.     }
  541.  
  542.     # search "again" (starting from current position)
  543.     search_find_next $editor_no 0
  544. }
  545.  
  546. proc search_find_next { editor_no {incr 1} } {
  547.     global editor
  548.     set t $editor($editor_no,text)
  549.  
  550.     # check/add the string to the find history
  551.     set list [lsearch -exact $editor(find_history) $editor(find_string)]
  552.     if {$list != -1} {
  553.         set editor(find_history) [lreplace $editor(find_history) $list $list]
  554.     }
  555.     set editor(find_history) [linsert $editor(find_history) 0 $editor(find_string)]
  556.  
  557.     set pos [$t index insert]
  558.     set line [lindex [split $pos "."] 0]
  559.     set x [lindex [split $pos "."] 1]
  560.     incr x $incr
  561.  
  562.     # attempt to find the string
  563.     if {$editor(match_case)} {
  564.         set pos [$t search -- $editor(find_string) $line.$x end]
  565.     } else {
  566.         set pos [$t search -nocase -- $editor(find_string) $line.$x end]
  567.     }
  568.  
  569.     # if found then move the insert cursor to that position, otherwise beep
  570.     if {$pos != ""} {
  571.         $t mark set insert $pos
  572.         $t see $pos
  573.  
  574.         # highlight the found word
  575.         set line [lindex [split $pos "."] 0]
  576.         set x [lindex [split $pos "."] 1]
  577.         set x [expr {$x + [string length $editor(find_string)]}]
  578.         $t tag remove sel 1.0 end
  579.         $t tag add sel $pos $line.$x
  580.         focus -force $t
  581.         update_status $editor_no
  582.         return 1
  583.     } else {
  584.         bell
  585.         return 0
  586.     }
  587. }
  588.  
  589. proc search_replace { editor_no } {
  590.     global editor
  591.  
  592.     set w .find
  593.  
  594.     # destroy the find window if it already exists
  595.     if {[winfo exists $w]} { destroy $w }
  596.  
  597.     # create the new "find" window
  598.     toplevel $w
  599.     wm transient $w .
  600.     wm title $w "Find & Replace"
  601.  
  602.     set f1 [frame $w.f1]
  603.     set f2 [frame $w.f2]
  604.  
  605.     set ft [frame $f1.top]
  606.     label $ft.text -text "Find What?" -width 15
  607.     set entry [combobox::combobox $ft.find -width 30 -value [lindex $editor(find_history) 0]]
  608.     pack $ft.text -side left -anchor nw -padx 4 -pady 4
  609.     pack $entry -side left -anchor nw -padx 4 -pady 4
  610.     pack $ft -side top -anchor nw
  611.  
  612.     set rt [frame $f1.bot]
  613.     label $rt.text -text "Replace with" -width 15
  614.     set replace [combobox::combobox $rt.replace -width 30 -value [lindex $editor(replace_history) 0]]
  615.     pack $rt.text -side left -anchor nw -padx 4 -pady 4
  616.     pack $replace -side left -anchor nw -padx 4 -pady 4
  617.     pack $rt -side top -anchor nw
  618.  
  619.     checkbutton $f1.case -text "Match Case?" -variable editor(match_case)
  620.     pack $f1.case -side left -padx 4 -pady 4
  621.  
  622.     # populate the combobox with the editor find history
  623.     foreach string $editor(find_history) {
  624.         $entry list insert end $string
  625.     }
  626.  
  627.     # populate the combobox with the editor replace history
  628.     foreach string $editor(replace_history) {
  629.         $replace list insert end $string
  630.     }
  631.  
  632.     button $f2.find -text "Find Next" -command "search_replace_command $editor_no $w $entry $replace find" -width 10 -pady 0
  633.     button $f2.find1 -text "Replace" -command "search_replace_command $editor_no $w $entry $replace replace" -width 10 -pady 0
  634.     button $f2.find2 -text "Replace All" -command "search_replace_command $editor_no $w $entry $replace all" -width 10 -pady 0
  635.     button $f2.cancel -text "Cancel" -command "destroy $w" -width 10 -pady 0
  636.     pack $f2.find -side top -padx 8 -pady 2
  637.     pack $f2.find1 -side top -padx 8 -pady 2
  638.     pack $f2.find2 -side top -padx 8 -pady 2
  639.     pack $f2.cancel -side top -padx 8 -pady 2
  640.  
  641.     pack $f1 -side left -anchor nw
  642.     pack $f2 -side left -anchor nw
  643.  
  644.     bind $entry.entry <Escape> "destroy $w"
  645.     bind $replace.entry <Escape> "destroy $w"
  646.  
  647.     focus -force $entry
  648.     centre_window $w
  649. }
  650.  
  651. proc search_replace_command { editor_no w entry replace command } {
  652.     global editor
  653.     set editor(find_string) [$entry get]
  654.     set editor(replace_string) [$replace get]
  655.  
  656.     # check/add the string to the find history
  657.     set list [lsearch -exact $editor(find_history) $editor(find_string)]
  658.     if {$list != -1} {
  659.         set editor(find_history) [lreplace $editor(find_history) $list $list]
  660.     }
  661.     set editor(find_history) [linsert $editor(find_history) 0 $editor(find_string)]
  662.  
  663.     # check/add the string to the replace history
  664.     set list [lsearch -exact $editor(replace_history) $editor(replace_string)]
  665.     if {$list != -1} {
  666.         set editor(replace_history) [lreplace $editor(replace_history) $list $list]
  667.     }
  668.     set editor(replace_history) [linsert $editor(replace_history) 0 $editor(replace_string)]
  669.  
  670.     switch -- $command {
  671.         "find" {
  672.             # search "again" (starting from current position)
  673.             search_find_next $editor_no 1
  674.         }
  675.         "replace" {
  676.             if {[replace_one $editor_no 0]} {
  677.                 search_find_next $editor_no 1
  678.             }
  679.         }
  680.         "all" {
  681.             set replace_count 0
  682.             if {[replace_one $editor_no 0]} {
  683.                 incr replace_count
  684.                 while {[replace_one $editor_no 1]} {
  685.                     incr replace_count
  686.                 }
  687.             }
  688.             tk_messageBox -icon info -title "Replace" -message "$replace_count item(s) replaced."
  689.             destroy $w
  690.         }
  691.     }
  692. }
  693.  
  694. proc replace_one { editor_no incr } {
  695.     global editor
  696.  
  697.     if {[search_find_next $editor_no $incr]} {
  698.         set t $editor($editor_no,text)
  699.         set selected [$t tag ranges sel]
  700.         set start [lindex $selected 0]
  701.         set end [lindex $selected 1]
  702.         $t delete $start $end
  703.         $t insert [$t index insert] $editor(replace_string)
  704.         return 1
  705.     } else {
  706.         return 0
  707.     }
  708. }
  709.  
  710. #== grep search (mulitple files) ===========================================#
  711.  
  712. proc grep_search { editor_no } {
  713.     global editor
  714.  
  715.     set w .grep
  716.  
  717.     # destroy the find window if it already exists
  718.     if {[winfo exists $w]} { destroy $w }
  719.  
  720.     # create the new "find" window
  721.     toplevel $w
  722.     wm transient $w .
  723.     wm title $w "Grep"
  724.  
  725.     set f1 [frame $w.f1]
  726.     set f2 [frame $w.f2]
  727.  
  728.     set ft [frame $f1.top]
  729.     label $ft.text -text "Find What?" -width 12
  730.     set entry [combobox::combobox $ft.find -width 30 -value [lindex $editor(find_history) 0]]
  731.     pack $ft.text -side left -anchor nw -padx 4 -pady 4
  732.     pack $entry -side left -anchor nw -padx 4 -pady 4
  733.     pack $ft -side top -anchor nw
  734.  
  735.     set fp [frame $f1.path]
  736.     label $fp.text -text "Search Path" -width 12
  737.     entry $fp.entry -width 30 -textvariable editor(grep_path)
  738.     pack $fp.text -side left -anchor nw -padx 4 -pady 4
  739.     pack $fp.entry -side left -anchor nw -padx 4 -pady 4
  740.     pack $fp -side top -anchor nw
  741.  
  742.     set editor(grep_ext) $editor(default_ext)
  743.     set fe [frame $f1.ext]
  744.     label $fe.text -text "Search Ext" -width 12
  745.     entry $fe.entry -width 30 -textvariable editor(grep_ext)
  746.     pack $fe.text -side left -anchor nw -padx 4 -pady 4
  747.     pack $fe.entry -side left -anchor nw -padx 4 -pady 4
  748.     pack $fe -side top -anchor nw
  749.  
  750.     checkbutton $f1.case -text "Match Case?" -variable editor(match_case)
  751.     pack $f1.case -side left -padx 4 -pady 4
  752.  
  753.     # populate the combobox with the editor find history
  754.     foreach string $editor(find_history) {
  755.         $entry list insert end $string
  756.     }
  757.  
  758.     button $f2.find -text "Start" -command "grep_search_now $w $entry" -width 10
  759.     button $f2.cancel -text "Cancel" -command "destroy $w" -width 10
  760.     pack $f2.find -side top -padx 8 -pady 4
  761.     pack $f2.cancel -side top -padx 8 -pady 4
  762.  
  763.     pack $f1 -side left -anchor nw
  764.     pack $f2 -side left -anchor nw
  765.  
  766.     bind $entry.entry <Return> "+grep_search_now $w $entry"
  767.     bind $entry.entry <Escape> "destroy $w"
  768.  
  769.     focus -force $entry
  770.     centre_window $w
  771. }
  772.  
  773. proc grep_search_now { w entry } {
  774.     global editor
  775.     set editor(find_string) [$entry get]
  776.     destroy $w
  777.  
  778.     # null string? do nothing
  779.     if {$editor(find_string) == ""} {
  780.         return
  781.     }
  782.  
  783.     # check/add the string to the find history
  784.     set list [lsearch -exact $editor(find_history) $editor(find_string)]
  785.     if {$list != -1} {
  786.         set editor(find_history) [lreplace $editor(find_history) $list $list]
  787.     }
  788.     set editor(find_history) [linsert $editor(find_history) 0 $editor(find_string)]
  789.  
  790.     # now get list of all files to open
  791.     # has file already been loaded? if not open it
  792.     # search file, display results in a window
  793.  
  794.     # make new editor window
  795.     set editor_no [make_editor]
  796.  
  797.     set editor($editor_no,title) "Grep Search Results: $editor(find_string)"
  798.     wm title . $editor($editor_no,title)
  799.  
  800.     set t $editor($editor_no,text)
  801.  
  802.     $t insert end "Search String: $editor(find_string)\nSearch Path: $editor(grep_path)\nSearch Ext: $editor(grep_ext)\n\n"
  803.  
  804.     # get list of files
  805.     variable file_list {}
  806.     grep_add_files ".[string trim $editor(grep_ext) .]" $editor(grep_path)
  807.  
  808.     set editor(grep_matches) 0
  809.  
  810.     set st [text .hidden]
  811.     set tag_no 0
  812.  
  813.     # search each file
  814.     foreach file [lsort -dictionary $file_list] {
  815.         set file_tag tag[incr tag_no]
  816.  
  817.         $t insert end "$file ...\n" $file_tag
  818.         $t see end
  819.         update
  820.  
  821.         set matches 0
  822.  
  823.         # open the file (if not open already?)
  824.         set fid [open $file]
  825.         $st insert end [read -nonewline $fid]
  826.         close $fid
  827.  
  828.         # search the file
  829.         # attempt to find the string
  830.         set current "1.0"
  831.  
  832.         while {1} {
  833.             if {$editor(match_case)} {
  834.                 set pos [$st search -- $editor(find_string) $current end]
  835.             } else {
  836.                 set pos [$st search -nocase -- $editor(find_string) $current end]
  837.             }
  838.  
  839.             if {$pos != ""} {
  840.                 incr matches
  841.  
  842.                 set line [lindex [split $pos .] 0]
  843.                 set current "$line.end"
  844.  
  845.                 set tag tag[incr tag_no]
  846.                 set data [string trim [$st get "$line.0" "$line.end"]]
  847.                 $t insert end "\t$line: $data\n" $tag
  848.  
  849.                 set bg [$t cget -background]
  850.                 $t tag bind $tag <Enter> "$t tag configure $tag -background skyblue"
  851.                 $t tag bind $tag <Leave> "$t tag configure $tag -background $bg"
  852.  
  853.                 $t tag bind $tag <1> [list grep_click $file $pos]
  854.             } else {
  855.                 break
  856.             }
  857.         }
  858.  
  859.         # remove contents from file
  860.         $st delete 1.0 end
  861.  
  862.         # configure the "tag" for highlighting purposes
  863.         if {$matches} {
  864.             $t insert end "\n"
  865.             incr editor(grep_matches) $matches
  866.         } else {
  867.             $t delete $file_tag.first $file_tag.last
  868.         }
  869.     }
  870.  
  871.     destroy $st
  872.  
  873.     $t insert end "\n[llength $file_list] file(s) were searched, $editor(grep_matches) match(es) were found.\n"
  874.     $t insert end "Move the mouse over any search result and click to open the file and display the match.\n"
  875.     $t see end
  876.  
  877.     # clear the status - default is "not modified"
  878.     set editor($editor_no,status) ""
  879. }
  880.  
  881. proc grep_add_files { ext dir } {
  882.     variable file_list
  883.  
  884.     set pattern [file join $dir *]
  885.  
  886.     foreach filename [glob -nocomplain $pattern] {
  887.         if {[file isdirectory $filename]} {
  888.             grep_add_files $ext $filename
  889.         }
  890.  
  891.         if {[file isfile $filename]} {
  892.             if {[string tolower [file extension $filename]] == [string tolower $ext]} {
  893.                 lappend file_list $filename
  894.             }
  895.         }
  896.     }
  897. }
  898.  
  899. proc grep_click { file pos } {
  900.     global editor
  901.  
  902.     # is the file already in memory?
  903.     set active 0
  904.     foreach name [lsort -dictionary [array names editor *,file]] {
  905.         set no [lindex [split $name ","] 0]
  906.         if {$editor($no,status) != "CLOSED" && [string equal -nocase $editor($no,file) $file]} {
  907.             set editor_no $no
  908.             set active 1
  909.             break
  910.         }
  911.     }
  912.     if {!$active} {
  913.         set editor_no [make_editor $file 0 0]
  914.     }
  915.  
  916.     set t $editor($editor_no,text)
  917.     make_window_active $editor_no
  918.     $t mark set insert $pos
  919.     $t see insert
  920. }
  921.  
  922. #== goto_line =======================================================#
  923.  
  924. proc goto_line { editor_no } {
  925.     global editor
  926.  
  927.     set w .goto
  928.  
  929.     # destroy the find window if it already exists
  930.     if {[winfo exists $w]} { destroy $w }
  931.  
  932.     # create the new "goto" window
  933.     toplevel $w
  934.     wm transient $w .
  935.     wm title $w "Goto Line"
  936.  
  937.     label $w.text -text "Goto Line"
  938.     entry $w.goto -width 6 -validate key -validatecommand "validate_number %W %P"
  939.     pack $w.text $w.goto -side left -anchor nw
  940.  
  941.     bind $w.goto <Return> "+goto_line_no $editor_no $w"
  942.     bind $w.goto <Escape> "destroy $w"
  943.     focus -force $w.goto
  944.  
  945.     centre_window $w
  946. }
  947.  
  948. proc validate_number { w new_value } {
  949.     if {[string is integer $new_value]} {
  950.         return 1
  951.     } else {
  952.         bell
  953.         return 0
  954.     }
  955. }
  956.  
  957. proc goto_line_no { editor_no w } {
  958.     global editor
  959.     set line_no [$w.goto get]
  960.     destroy $w
  961.  
  962.     catch {
  963.         set t $editor($editor_no,text)
  964.         $t mark set insert $line_no.0
  965.         $t see insert
  966.     }
  967. }
  968.  
  969. #=================================================================#
  970.  
  971. # right click on any word and a popup menu offers the "find WORD" option.
  972. # this is the same as the user pressing "Search-Find" (ctrl-f) then entering the word to search
  973.  
  974. proc popup_text_menu {editor_no x y} {
  975.     global editor
  976.     set t $editor($editor_no,text)
  977.  
  978.     # place the insert cursor at the mouse pointer
  979.     $t mark set insert @$x,$y
  980.     set pos [$t index insert]
  981.  
  982.     # get the first being clicked-on
  983.     set string [string trim [$t get "insert wordstart" "insert wordend"]]
  984.  
  985.     # create the pop-up menu for "find word"
  986.     set pw .popup
  987.     catch {destroy $pw}
  988.     menu $pw -tearoff false
  989.  
  990.     # if the mouse was clicked over a word then offer this word for "find"
  991.     if {$string != ""} {
  992.         $pw add command -label "Find \"$string\"" -command [list popup_find_text $editor_no $string]
  993.  
  994.         # if the string is a procedure name then allow the user to go directly to the procedure definition
  995.         foreach procs $editor($editor_no,procs) {
  996.             set proc [lindex $procs 0]
  997.             set no [lindex $procs 1]
  998.             if {$proc == $string} {
  999.                 $pw add command -label "Goto \"$string\" definition" -command "$t mark set insert mark_$no;$t see insert;update_status $editor_no"
  1000.                 break
  1001.             }
  1002.         }
  1003.  
  1004.         $pw add separator
  1005.     }
  1006.     # display the "undo" option
  1007.     $pw add command -label "Undo" -command "$t undo" -underline 0 -accelerator Ctrl+Z
  1008.     $pw add separator
  1009.     # display the usual cut/copy/paste options
  1010.     $pw add command -label "Cut" -command "tk_textCut $t" -underline 0 -accelerator Ctrl+X
  1011.     $pw add command -label "Copy" -command "tk_textCopy $t" -underline 0 -accelerator Ctrl+C
  1012.     $pw add command -label "Paste" -command "tk_textPaste $t" -underline 0 -accelerator Ctrl+V
  1013.     tk_popup $pw $x $y
  1014. }
  1015.  
  1016. proc popup_find_text { editor_no string } {
  1017.     global editor
  1018.     set editor(find_string) $string
  1019.     search_find_next $editor_no
  1020. }
  1021.  
  1022. proc toggle_word_wrap { editor_no } {
  1023.     global editor
  1024.  
  1025.     set t $editor($editor_no,text)
  1026.     switch -- $editor($editor_no,wordwrap) {
  1027.         1 { $t configure -wrap word }
  1028.         default { $t configure -wrap none }
  1029.     }
  1030. }
  1031.  
  1032. proc view_font_size { editor_no increment } {
  1033.     global editor
  1034.     set t $editor($editor_no,text)
  1035.  
  1036.     set font [$t cget -font]
  1037.     set size [lindex $font 1]
  1038.     incr size $increment
  1039.     set font [lreplace $font 1 1 $size]
  1040.  
  1041.     $t configure -font $font
  1042. }
  1043.  
  1044. #== configure_window =================================================#
  1045.  
  1046. proc configure_window {} {
  1047.     # trap the EXIT [X] button "exit editor"
  1048.     wm protocol . WM_DELETE_WINDOW "exit_editor"
  1049.  
  1050.     # on windows we can maximise the window by default
  1051.     global tcl_platform
  1052.     if {$tcl_platform(platform) == "windows" && [info tclversion] >= 8.3} {
  1053.         wm state . zoomed
  1054.     }
  1055. }
  1056.  
  1057. #== make_editor =====================================================#
  1058.  
  1059. # this procedure makes a new editor window and creates all necessary bindings
  1060. # this procudure is called on start-up to load the files specified on the command line and for every "file open"
  1061.  
  1062. proc make_editor { {file ""} {display_window 1} {highlight 1} } {
  1063.     global editor editor_no splash_status
  1064.  
  1065.     set w [frame .w[incr editor_no]]
  1066.  
  1067.     set editor($editor_no,window) $w
  1068.     set editor($editor_no,file) $file
  1069.     set editor($editor_no,title) [file tail $file]
  1070.     set editor($editor_no,status) ""
  1071.     set editor($editor_no,procs) ""
  1072.     set editor($editor_no,syntax) 0
  1073.  
  1074.     if {$file == ""} {
  1075.         set data ""
  1076.         set file "Untitled"
  1077.         # new files are always writable
  1078.         set editor($editor_no,writable) 1
  1079.     } elseif {[catch {set fid [open $file]} msg]} {
  1080.         tk_messageBox -type ok -icon error -title "File Open Error" \
  1081.             -message "There was an error opening file \"$file\"; $msg."
  1082.         return
  1083.     } else {
  1084.         if {!$display_window} {
  1085.             set splash_status "Loading [file tail $file] ..."
  1086.             update
  1087.         }
  1088.  
  1089.         set data [read -nonewline $fid]
  1090.         close $fid
  1091.         # record whether or not the file can be saved (is the file writable?)
  1092.         set editor($editor_no,writable) [file writable $file]
  1093.         if {!$editor($editor_no,writable)} {
  1094.             set editor($editor_no,status) "READ ONLY"
  1095.         }
  1096.     }
  1097.  
  1098.     # create the main display frames (1 = editor, 2 = status/procedure window)
  1099.     set f1 [frame $w.f1]
  1100.     set f2 [frame $w.f2]
  1101.  
  1102.     set t $f1.text
  1103.     set editor($editor_no,text) $t
  1104.  
  1105.     # save the file extension, this is used for syntax highlighting commands
  1106.     set editor($editor_no,extension) [string tolower [file extension $file]]
  1107.  
  1108.     set tx $f1.tx
  1109.     set ty $f1.ty
  1110.  
  1111.     # has a font been specified in the configuration file (.ml_cfgrc) for this file type?
  1112.     if {[array names editor font,$editor($editor_no,extension)] != ""} {
  1113.         set font $editor(font,$editor($editor_no,extension))
  1114.     } else {
  1115.         set font $editor(font)
  1116.     }
  1117.  
  1118.     supertext::text $t -xscrollcommand "$tx set" -yscrollcommand "$ty set" -exportselection 1 \
  1119.         -wrap none -font $font -tabs {1c 2c 3c 4c 5c 6c} -background #e7e7e7
  1120.  
  1121.     $t insert end $data
  1122.     $t reset_undo
  1123.  
  1124.     set editor($editor_no,wordwrap) 0
  1125.  
  1126.     # provide a calling routine for the $t/text procedure to trap insert/delete commands
  1127.     rename $t $t\_
  1128.     proc $t {command args} "
  1129.         global editor
  1130.  
  1131.         # store line number where insert/delete starts
  1132.         if \{\[string equal \$command insert\] || \[string equal \$command delete\]\} \{
  1133.             set line1 \[lindex \[split \[$t\_ index insert\] .\] 0\]
  1134.  
  1135.             if {!$editor($editor_no,writable)} {
  1136.                 bell
  1137.                 return \"\"
  1138.             }
  1139.         \}
  1140.  
  1141.         # perform the specified command
  1142.         set result \[eval uplevel \[list $t\_ \$command \$args\]\]
  1143.  
  1144.         if \{\[string equal \$command insert\] || \[string equal \$command delete\]\} \{
  1145.             # insert/delete? syntax highlight the newly inserted text & checkall procedures
  1146.             set line2 \[lindex \[split \[$t\_ index insert\] .\] 0\]
  1147.             syntax_highlight $editor_no \$line1 \$line2
  1148.             validate_procedures $editor_no
  1149.             set editor($editor_no,status) MODIFIED
  1150.             $t see insert
  1151.         \}
  1152.  
  1153.         if \{\[string equal \$command undo\]\} \{
  1154.             set editor($editor_no,status) MODIFIED
  1155.         \}
  1156.  
  1157.         return \$result"
  1158.  
  1159.     scrollbar $tx -command "$t xview" -orient h
  1160.     pack $tx -side bottom -fill x
  1161.  
  1162.     scrollbar $ty -command "$t yview"
  1163.     pack $ty -side right -fill y
  1164.  
  1165.     pack $t -side left -fill both -expand yes
  1166.  
  1167.     # update the screen/display status after every key/button release
  1168.     bind $t <KeyRelease> "update_status $editor_no"
  1169.     bind $t <ButtonRelease> "update_status $editor_no"
  1170.  
  1171.     # keyboard/accelerator bindings
  1172.     bind $t <Control-f> "search_find $editor_no;break"
  1173.     bind $t <Control-F> "search_find $editor_no;break"
  1174.     bind $t <F3> "search_find_next $editor_no;break"
  1175.     bind $t <Control-h> "search_replace $editor_no;break"
  1176.     bind $t <Control-H> "search_replace $editor_no;break"
  1177.  
  1178.     bind $t <Control-X> "tk_textCut $t;break"
  1179.     bind $t <Control-C> "tk_textCopy $t;break"
  1180.     bind $t <Control-V> "tk_textPaste $t;break"
  1181.  
  1182.     # control-s, shortcut to save file
  1183.     bind $t <Control-s> "save_file $editor_no;break"
  1184.     bind $t <Control-S> "save_file $editor_no;break"
  1185.  
  1186.     if {$::tcl_platform(platform) == "windows"} {
  1187.         bind $t <Control-p> "print_file $editor_no;break"
  1188.         bind $t <Control-P> "print_file $editor_no;break"
  1189.     }
  1190.  
  1191.     bind $t <Control-plus> "view_font_size $editor_no 1"
  1192.     bind $t <Control-minus> "view_font_size $editor_no -1"
  1193.  
  1194.     # bind the right mouse click to select the current word and display a pop-up menu
  1195.     bind $t <ButtonPress-3> "popup_text_menu $editor_no %x %y"
  1196.  
  1197.     # bind the double click on text brace to select the braces
  1198.     bind $t <Double-Button> {if {[selectClosingBrace %W]} {break}}
  1199.  
  1200.     # bind control-g for "goto line number"
  1201.     bind $t <Control-g> "goto_line $editor_no;break"
  1202.     bind $t <Control-G> "goto_line $editor_no;break"
  1203.  
  1204.     # PCS time saving option for converting 4 spaces to Tab
  1205.     bind $t <F10> "replace_4_spaces $editor_no;break"
  1206.  
  1207.     # see the syntax_highlighting procedure for details of each tag    
  1208.  # BK have changed that Verdana from 9 to 12...
  1209.     $t tag configure command -foreground blue
  1210.     $t tag configure number -foreground DarkGreen
  1211.     $t tag configure proc -foreground blue -font {Verdana 12 bold}
  1212.     $t tag configure comment -foreground green4
  1213.     $t tag configure variable -foreground red
  1214.     $t tag configure string -foreground purple
  1215.     $t tag configure sel -background skyblue
  1216.  
  1217.     # create the right-hand frame
  1218.  # BK Arial 8 is too small, changed to 10...
  1219.     text $f2.procs -xscrollcommand "$f2.tx set" -yscrollcommand "$f2.ty set" \
  1220.         -wrap none -font {Arial 10} -background #ffc800 -width 30 -cursor arrow
  1221.     scrollbar $f2.tx -command "$f2.procs xview" -orient h
  1222.     pack $f2.tx -side bottom -fill x
  1223.     scrollbar $f2.ty -command "$f2.procs yview"
  1224.     pack $f2.ty -side right -fill y
  1225.     pack $f2.procs -side left -fill both -expand yes
  1226.  
  1227.     set editor($editor_no,status_window) $f2.procs
  1228.  
  1229.     # pack the 3 frames
  1230.     pack $f1 -side left -fill both -expand yes
  1231.     pack $f2 -side left -fill y
  1232.  
  1233.     focus -force $t
  1234.     $t mark set insert 1.0
  1235.  
  1236.     if {$highlight} {
  1237.         syntax_highlight $editor_no 1 end
  1238.     }
  1239.  
  1240.     if {$display_window} {
  1241.         make_window_active $editor_no
  1242.     }
  1243.  
  1244.     return $editor_no
  1245. }
  1246.  
  1247. proc replace_4_spaces { editor_no } {
  1248.     global editor
  1249.     set t $editor($editor_no,text)
  1250.  
  1251.     # if the cursor is at the start of 4 spaces then replace them with a tab character
  1252.     if {[$t get "insert" "insert+4c"] == "    "} {
  1253.         $t delete "insert" "insert+4c"
  1254.         $t insert "insert" "\t"
  1255.     } elseif {[$t get "insert" "insert+5c"] == "\t    "} {
  1256.         $t delete "insert" "insert+5c"
  1257.         $t insert "insert" "\t\t\t"
  1258.     } elseif {[$t get "insert" "insert+1c"] == "\t"} {
  1259.         $t delete "insert" "insert+1c"
  1260.         $t insert "insert" "\t\t"
  1261.     }
  1262.  
  1263.     set pos [$t index "insert"]
  1264.     set line_no [expr {[lindex [split $pos "."] 0] + 1}]
  1265.     $t mark set insert "$line_no.0"
  1266.     $t see $pos
  1267. }
  1268.  
  1269. #== open file =======================================================#
  1270.  
  1271. proc open_file { editor_no } {
  1272.     global editor
  1273.     global file_types
  1274.  
  1275.     set file $editor($editor_no,file)
  1276.     if {$file != ""} {
  1277.         set pwd [file dirname $file]
  1278.         set ext $editor($editor_no,extension)
  1279.     } else {
  1280.         set pwd [pwd]
  1281.         set ext $editor(default_ext)
  1282.     }
  1283.  
  1284.     set file [tk_getOpenFile -title "Open File" -initialdir $pwd -initialfile "*.[string trim $ext .]" \
  1285.         -defaultextension ".[string trim $ext .]" -filetypes $file_types]
  1286.  
  1287.     if {$file != ""} {
  1288.         make_editor $file
  1289.     }
  1290. }
  1291.  
  1292. #== save file =======================================================#
  1293.  
  1294. proc save_file { editor_no } {
  1295.     global editor
  1296.     set file $editor($editor_no,file)
  1297.  
  1298.     if {$file == ""} {
  1299.         save_file_as $editor_no
  1300.     } else {
  1301.         set fid [open $file w+]
  1302.         set t $editor($editor_no,text)
  1303.         puts -nonewline $fid [$t get 1.0 end]
  1304.         close $fid
  1305.         set editor($editor_no,status) ""
  1306.  
  1307.         # previously we undid the "undo" status after saving
  1308.         # now allow undo to go back since the file was originally opened
  1309.     }
  1310. }
  1311.  
  1312. #== save file as ====================================================#
  1313.  
  1314. proc save_file_as { editor_no } {
  1315.     global editor
  1316.     global file_types
  1317.     set file $editor($editor_no,file)
  1318.  
  1319.     set file [tk_getSaveFile -title "Save File" -initialdir [pwd] -initialfile $file -filetypes $file_types]
  1320.  
  1321.     if {$file != ""} {
  1322.         set fid [open $file w+]
  1323.         set t $editor($editor_no,text)
  1324.         puts -nonewline $fid [$t get 1.0 end]
  1325.         close $fid
  1326.         set editor($editor_no,status) ""
  1327.         set editor($editor_no,file) $file
  1328.         set editor($editor_no,title) [file tail $file]
  1329.         wm title . $editor($editor_no,title)
  1330.  
  1331.         # reset the undo status
  1332.         set t $editor($editor_no,text)
  1333.         $t reset_undo
  1334.  
  1335.         # update the file extension, this is used for syntax highlighting commands
  1336.         set editor($editor_no,extension) [string tolower [file extension $file]]
  1337.     }
  1338. }
  1339.  
  1340. #== close window ====================================================#
  1341.  
  1342. proc close_window { editor_no {action ""} } {
  1343.     global editor
  1344.  
  1345.     # check status of window before closing
  1346.     while {$editor($editor_no,status) == "MODIFIED"} {
  1347.         set option [tk_messageBox -title "Save Changes?" -icon question -type yesnocancel -default yes \
  1348.             -message "File \"$editor($editor_no,file)\" has been modified.\nDo you want to save the changes?"]
  1349.  
  1350.         if {$option == "yes"} {
  1351.             save_file $editor_no
  1352.         } elseif {$option != "no"} {
  1353.             return 0
  1354.         } else {
  1355.             break
  1356.         }
  1357.     }
  1358.  
  1359.     destroy $editor($editor_no,window)
  1360.     set editor($editor_no,status) "CLOSED"
  1361.  
  1362.     # make another window active - if any?
  1363.     set active 0
  1364.     foreach name [lsort -dictionary [array names editor *,file]] {
  1365.         set no [lindex [split $name ","] 0]
  1366.         if {$editor($no,status) != "CLOSED"} {
  1367.             make_window_active $no
  1368.             set active 1
  1369.             break
  1370.         }
  1371.     }
  1372.  
  1373.     if {!$active && $action != "exit"} { make_editor }
  1374.  
  1375.     return 1
  1376. }
  1377.  
  1378. #== exit editor =====================================================#
  1379.  
  1380. proc exit_editor {} {
  1381.     global editor
  1382.     global syntax
  1383.  
  1384.     # first save the configuration file ".ml_cfgrc"
  1385.  
  1386.     # BK set fid [open [file join $editor(initial_dir) ".ml_cfgrc"] w]
  1387.  set fid [open [file join $editor(root_dir) ".ml_cfgrc"] w]
  1388.  
  1389.     puts $fid "# ML editor configuration file - AUTO GENERATED"
  1390.     puts $fid "# DO NOT EDIT THIS FILE WITH \"ML\", USE ANOTHER EDITOR (BECAUSE ML WILL OVERWRITE YOUR CHANGES)"
  1391.     puts $fid ""
  1392.  
  1393.     puts $fid "# find & file history"
  1394.     set file_history ""
  1395.     foreach name [lsort -dictionary [array names editor *,status]] {
  1396.         set no [lindex [split $name ","] 0]
  1397.         if {$editor($no,status) != "CLOSED"} {
  1398.             if {$editor($no,file) != ""} {
  1399.                 lappend file_history $editor($no,file)
  1400.             }
  1401.         }
  1402.     }
  1403.     puts $fid "set editor(find_history) [list [lrange $editor(find_history) 0 19]]"
  1404.     puts $fid "set editor(replace_history) [list [lrange $editor(replace_history) 0 19]]"
  1405.     puts $fid "set editor(file_history) [list $file_history]"
  1406.     puts $fid ""
  1407.  
  1408.     puts $fid "# fonts for each file type"
  1409.     puts $fid "# to specify/change the font for a specific file type insert a line as follows;"
  1410.     puts $fid "# set editor(font,extension) {FontName FontSize}"
  1411.     foreach font [lsort [array names editor font*]] {
  1412.         puts $fid [list set editor($font) $editor($font)]
  1413.     }
  1414.     puts $fid ""
  1415.  
  1416.     puts $fid "# default extension (you'll need to edit the file manually to change the default extension)"
  1417.     puts $fid "set editor(default_ext) $editor(default_ext)"
  1418.     puts $fid ""
  1419.  
  1420.     puts $fid "# syntax highlight for different file types"
  1421.     puts $fid "# set syntax(.extension,command) 1"
  1422.     foreach syn [lsort [array names syntax]] {
  1423.         set ext [lindex [split $syn ","] 0]
  1424.         if {$ext != ".tcl"} {
  1425.             puts $fid [list set syntax($syn) $syntax($syn)]
  1426.         }
  1427.     }
  1428.  
  1429.     close $fid
  1430.  
  1431.     # close all files in reverse order... this is done so we don't end up displaying all files (see close_window)
  1432.     foreach name [lsort -dictionary -decreasing [array names editor *,status]] {
  1433.         set no [lindex [split $name ","] 0]
  1434.         if {$editor($no,status) != "CLOSED"} {
  1435.             if {![close_window $no "exit"]} {
  1436.                 return
  1437.             }
  1438.         }
  1439.     }
  1440.  
  1441.     # exit, close main window
  1442.     destroy .
  1443. }
  1444.  
  1445. #== print file =====================================================#
  1446.  
  1447. proc gdi_init { title } {
  1448.     global gdi
  1449.  
  1450.     # display the printer dialog, get response {printer exit_status}
  1451.     set printer [printer dialog select]
  1452.     if {[lindex $printer 1] != 1} {
  1453.         return 0
  1454.     }
  1455.  
  1456.     # set the "hdc", this is used for all graphics/data output
  1457.     set gdi(hdc) [lindex $printer 0]
  1458.  
  1459.     printer job -hdc $gdi(hdc) start -name $title
  1460.  
  1461.     # process the printer attributes, we need to page margins and pixels per inch
  1462.     foreach row [printer attr -hdc $gdi(hdc)] {
  1463.         set option [lindex $row 0]
  1464.         set values [lindex $row 1]
  1465.         switch -exact -- $option {
  1466.             "page dimensions" {
  1467.                 set gdi(width) [lindex $values 0]
  1468.                 set gdi(height) [lindex $values 1]
  1469.             }
  1470.             "page minimum margins" {
  1471.                 set gdi(left) [lindex $values 0]
  1472.                 set gdi(top) [lindex $values 1]
  1473.                 set gdi(right) [lindex $values 2]
  1474.                 set gdi(bottom) [lindex $values 3]
  1475.             }
  1476.             "pixels per inch" {
  1477.                 set gdi(resx) [lindex $values 0]
  1478.                 set gdi(resy) [lindex $values 1]
  1479.             }
  1480.         }
  1481.     }
  1482.  
  1483.     return 1
  1484. }
  1485.  
  1486. proc gdi_x { x } {
  1487.     # convert x which is specified as a character position to the pixel position
  1488.     global gdi
  1489.     set x [expr {(($x - 1) / 11.0) * $gdi(resx) + $gdi(left)}]
  1490.     return $x
  1491. }
  1492.  
  1493. proc gdi_y { y } {
  1494.     # convert y which is specified as a character position to the pixel position
  1495.     global gdi
  1496.     set y [expr {(($y - 1) / 6.0) * $gdi(resy) + $gdi(top)}]
  1497.     return $y
  1498. }
  1499.  
  1500. proc gdi_inches { i axis } {
  1501.     # convert i which is specified in inches to a pixel size (eg: 1 inch may equal 600 pixels)
  1502.     global gdi
  1503.     set i [expr {$i * $gdi(res$axis)}]
  1504.     return $i
  1505. }
  1506.  
  1507. proc gdi_page { command } {
  1508.     # gdi_page start/end
  1509.     global gdi
  1510.     printer page -hdc $gdi(hdc) $command
  1511. }
  1512.  
  1513. proc gdi_close {} {
  1514.     global gdi
  1515.     printer job -hdc $gdi(hdc) end
  1516.     printer close
  1517. }
  1518.  
  1519. # the print file command relies on the packages "printer" & "gdi" to be installed somewhere
  1520. # the system uses the font for the current window, to print smaller make the font smaller
  1521.  
  1522. proc print_file { editor_no } {
  1523.     global gdi editor
  1524.  
  1525.     # load the packages we require, if not installed then just result in an error
  1526.     package require printer
  1527.     package require gdi
  1528.  
  1529.     # initialise gdi print device
  1530.     if {![gdi_init "ML: $editor($editor_no,title)"]} { return }
  1531.  
  1532.     set t $editor($editor_no,text)
  1533.  
  1534.     set font [$t cget -font]
  1535.  
  1536.     # get the number of lines (keep insert cursor in original place)
  1537.     set insert [$t index insert]
  1538.     $t mark set insert end
  1539.     set lines [lindex [split [$t index insert] .] 0]
  1540.     $t mark set insert $insert
  1541.  
  1542.     set page_no 0
  1543.     set y 0
  1544.  
  1545.     set datetime [clock format [clock seconds] -format "%A, %d %B %Y - %I:%M %p"]
  1546.  
  1547.     # process each line
  1548.     for {set line 1} {$line <= $lines} {incr line} {
  1549.         set next [expr {$line + 1}]
  1550.         set text [$t get $line.0 $next.0]
  1551.  
  1552.         # before outputting text determine if new page is requried
  1553.         if {!$y} {
  1554.             gdi_page start
  1555.             incr page_no
  1556.  
  1557.  # BK changed Arial 13 to 14, 8 to 10...
  1558.             gdi text $gdi(hdc) [gdi_x 1] [gdi_y 0] -text $editor($editor_no,title) -font {Arial 14 bold} -justify left -anchor w
  1559.             gdi text $gdi(hdc) [gdi_x 1] [gdi_y 64] -text $datetime -font {Arial 10} -justify left -anchor w
  1560.             gdi text $gdi(hdc) [gdi_x 80] [gdi_y 64] -text "Page: $page_no" -font {Arial 10} -justify left -anchor e
  1561.  
  1562.             set y [gdi_y 2]
  1563.         }
  1564.  
  1565.         # now output the text for the source code
  1566.         gdi text $gdi(hdc) [gdi_x 4] $y -text $line -font $font -anchor ne -justify left
  1567.         set height [gdi text $gdi(hdc) [gdi_x 5] $y -text $text -font $font -anchor nw -justify left]
  1568.  
  1569.         set y [expr {$y + ($height / 2)}]
  1570.         if {$y > [gdi_y 62]} {
  1571.             gdi_page end
  1572.             set y 0
  1573.         }
  1574.     }
  1575.  
  1576.     if {$y} { gdi_page end }
  1577.  
  1578.     gdi_close
  1579. }
  1580.  
  1581. #== open the default windows ========================================#
  1582.  
  1583. global editor
  1584. global syntax
  1585. global editor_no
  1586. global file_types
  1587.  
  1588. set editor(version) "1.11"
  1589.  
  1590. set editor_no 0
  1591.  
  1592. set editor(current) ""
  1593.  
  1594. # set default file extension
  1595. set editor(default_ext) "tcl"
  1596. set editor(initial_dir) [pwd]
  1597. set editor(grep_path) $editor(initial_dir)
  1598.  
  1599. # BK...
  1600. set editor(root_dir) "/root"
  1601.  
  1602. # set default font - saved in the .ml_cfgrc file (user needs to change manually)
  1603. # BK change 9 to 12...
  1604. set editor(font) {Verdana 12}
  1605.  
  1606. # files loaded since last use of editor (see proc exit_editor)
  1607. set editor(file_history) {}
  1608.  
  1609. # find history (list of strings previously searched for)
  1610. set editor(find_history) {}
  1611. set editor(match_case) 0
  1612. set editor(replace_history) {}
  1613.  
  1614. # load the configuration file (if it exists/is readable)
  1615. # BK...
  1616. if {[file readable "/root/.ml_cfgrc"]} {
  1617.     source /root/.ml_cfgrc
  1618. }
  1619.  
  1620. # default the current find string to the last value
  1621. set editor(find_string) [lindex $editor(find_history) 0]
  1622. set editor(replace_string) [lindex $editor(replace_history) 0]
  1623.  
  1624. set file_types {
  1625.     {{All Files}    *          }
  1626.     {{TCL Scripts}    {.tcl}          }
  1627.     {{FastBase Source}    {.fb}          }
  1628.     {{Magix Source}    {.ms}          }
  1629.     {{Html}    {.html .htm}          }
  1630.     {{Text Files}    {.txt}          }}
  1631.  
  1632. # create a global array syntax(file_extension,commands)
  1633. # this is used by the "tag_word" procedure to detect words
  1634. foreach command [info commands] {
  1635.     set syntax(.tcl,$command) 1
  1636. }
  1637.  
  1638. # load the files specified on the command line
  1639. # if none then check the "editor(file_history)" variable as saved in the configuration file
  1640.  
  1641. set any_files 0
  1642.  
  1643. if {$argc} {
  1644.     foreach name $argv {
  1645.         # replace all backslashes with forward slashes so windows filenames will be "globbed" ok.
  1646.         regsub -all "\\\\" $name "/" name
  1647.         foreach name [glob -nocomplain $name] {
  1648.             make_editor $name 0 0
  1649.             set any_files 1
  1650.         }
  1651.     }
  1652. } elseif {$editor(file_history) != ""} {
  1653.     foreach file $editor(file_history) {
  1654.         if {[file readable $file]} {
  1655.             make_editor $file 0 0
  1656.             set any_files 1
  1657.         }
  1658.     }
  1659. }
  1660.  
  1661. after idle {
  1662.     destroy .splash
  1663.     wm deiconify .
  1664. }
  1665.  
  1666. # configure the window and menus
  1667. configure_window
  1668.  
  1669. # if no files loaded then open a blank editor window
  1670. if {!$any_files} {
  1671.     make_editor
  1672. } else {
  1673.     make_window_active 1
  1674. }
  1675.