Updated 2014-06-07 21:10:44 by uniquename

uniquename - 2013aug05

At the top of my 'bio' page at uniquename, I have mentioned a set of 'Freedom Environment' utilities --- many of which are implemented with Tcl-Tk --- that are available from www.freedomenv.com.

Also on that 'bio' page, I committed to contributing to this wiki five 'QuikPlot' utilities, adapted into 'stand-alone' scripts taken from scripts integrated into one of the FE subsystems. I have posted two of those plot utilities, so far.

In addition to the plot utilities, there is one other 'Freedom Environment' utility that I would like to contribute to these pages --- a text file browser utility that I call 'xpg'.

The 'xpg' utility has a powerful 'Show-All-Match-Lines' feature that I have used over and over in helping me write the 35-plus Tk scripts that I have donated (so far) to these wiki pages.

One reason I would like to donate this utility to these pages is because I get tired of reading web postings claiming that Tcl-Tk is dead. I think this 'xpg' utility, that I use almost daily, shows that Tcl-Tk is not dead. It lives every day --- at least for me.

The features of the 'xpg' utility have evolved over multiple years, but I have been using it several years now with no major enhancements. So I think it is appropriate to donate the code to this wiki at this time.

Here is an image that demonstrates what the GUI looks like.

In this image, the 'xpg' utility is showing the text file 'shofil.tk', which is the Tk code that is showing this GUI.

You can see that the GUI includes the typical 'Exit' and 'Help' buttons, as well as a 'Search string' entry field that is typical of applications that browse text files.

The really powerful feature of this 'xpg' utility comes from the 'SAM' (Show All Matches) button on the GUI.

If the user clicks on the SAM button, instead of the Search button, the 'xpg' utility uses itself to show a listing of match lines in a new popup window.

In fact, the 'xpg' utility uses itself to show the Help file that is shown when the user clicks on the 'Help' button.

Another couple of buttons that need a little explaining are the '<-Ybar->' button and the 'OtherOpts' button.

The '<-Ybar->' button toggles the vertical scrollbar back-and-forth, from the right side of the GUI to the left, and back. I use this quite frequently, because I often like to have the scrollbar on the left --- as I am editing a Tk script in the 'Scite' text editor, while having some sample code in 'xpg', for reference.

The 'OtherOpts' button shows a 'hidden' frame of additional search options (and a 'WinColor' button and a 'TxtFont' button) --- just below the 'Search(again)' and 'SAM' buttons.

The 'OtherOpts' frame can be seen in a screenshot that is posted below the code on this page.

Both the '<-Ybar->' button and the 'OtherOpts' button are implemented by use of the Tk 'pack forget' command.

One other feature of the GUI that is worth pointing out at this point is the '+/-' label on the GUI, just to the right of the 'SAM' button. The number 0 appears just to the right of that string, and there is a popout menu there that allows the user to change the number.

That number allows the user to request that NOT JUST THE MATCH LINES be shown when the 'SAM' button is clicked, but ALSO N-lines above and below each match line.

This can be handy when one is, for example, looking for info on an issue in a large README file, and one needs to see the lines above and below match lines in order to see the lines that are needed to fully describe the issue -- the complete sentences containing the search-word(s).

The code

Below, I provide the Tk script code for this 'xpg' utility.

This utility is actually composed of about 8 files (four shell scripts, 3 Tk scripts, and a help file) --- but the central guts of the utility is a Tk script that I call 'shofil.tk'.

The other two Tk scripts are color-selector and font-selector utility scripts that I have already posted on this wiki --- at A non-obfuscated color selector GUI and YAFSG - Yet Another Font Selector GUI.

The 4 shell scripts have fairly descriptive names:

  • findANDshow_stringsINfile_plusminusNlines.sh
  • findANDshow_NOTstringsINfile.sh
  • set_localoutlist.sh
  • xpg

The 'xpg' script is a 'wrapper' script for the 'shofil.tk' script.

I will post the code for the 4 shell scripts below the code for 'shofil.tk', below.

The 2 'find' shell scripts use the 'awk' command to perform their function. And the other 2 shell scripts use quite standard Linux/Unix/BSD commands to do their work. So ALL of this contributed code should work on most Linux, Unix, and BSD systems. If there are small differences in the way the 'awk' command works on a particular system, you can probably get the 2 'find' scripts to work by making no more than a few minor changes.

And since Apple Mac operating systems are based on BSD systems, it is quite likely that this set of scripts will work on Mac's --- but I have no Mac machine on which to try this (nor do I want to spend the time to do such testing --- I have too many other Tcl-Tk projects to address).

But if anyone tries out this set of code on a Mac, I would be interested to hear whether it all worked out.

In relation to Microsoft operating systems, the 'shofil.tk' Tk script --- and the color-selector and font-selector Tk scripts --- should probably work with few changes. However, the 'SAM' button, which uses the 2 'find' scripts, will not work unless the user has a package installed that includes the 'awk' program --- and even then some changes to the scripts will probably have to be made.

Another alternative would be to rewrite the 2 'find' scripts in C code, and replace the 2 find scripts with compiled C code. (I leave it to others to handle porting these files to a Microsoft OS.)

I followed my usual 'canonical' structure for Tk code in writing the 'shofil.tk' script:
  0) Set general window & widget parms (win-name, win-position,
     win-color-scheme, fonts, widget-geometry-parms, win-size-control,
     text-array-for-labels-etc).

  1a) Define ALL frames (and sub-frames, if any).
  1b) Pack   ALL frames and sub-frames.

  2) Define & pack all widgets in the frames, frame by frame.
              Within each frame, define ALL the widgets.
              Then pack the widgets.

  3) Define keyboard and mouse/touchpad/touch-sensitive-screen action
     BINDINGS, if needed.

  4) Define PROCS, if needed.

  5) Additional GUI initialization (typically with one or more of
     the procs), if needed.

This Tk coding structure is discussed in more detail on the page A Canonical Structure for Tk Code --- and variations.

This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in other scripts (code re-use).

I call your attention to step-zero. One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.

Experimenting with the GUI

As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on all of the 'pack' commands for the frames and widgets.

That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window.

I think that I have used a nice choice of the 'pack' parameters for the 'shofil.tk' GUI. The labels and buttons and checkbuttons stay fixed in size and relative-location as the window is re-sized --- while the entry field x-exands as the window x-expands --- and the 'text' widget and its x and y scrollbars expand/contract appropriately as the window size is changed.

You can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various frames and widgets --- to get the widget behavior that you want.

___

In addition, you might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families.

In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.

I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

Some features in the code

That said, here's the code for 'shofil.tk' --- with plenty of comments to describe what most of the code-sections are doing.

You can look at the top of the PROCS section of the code to see a list of the procs used in this script, along with brief descriptions of how they are called and what they do.

The two main procs that do the search functions are 'search4string' and 'all_matches2string'. The second of these implements the 'SAM' button.

Another main proc is 'getfile2textwidget' which uses the Tcl 'gets' command to load lines from the file into the 'text' widget.

It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this.

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of friends bashing friends in the face. (I wonder how many are still friends?)

 Code for 'shofil.tk'

#!/usr/bin/wish -f
##
## NOTE:
## If 'wish' is in /usr/local/bin, for example, instead of /usr/bin, then
## the (root) user could make a soft-link named /usr/bin/wish that
## points to /usr/local/bin/wish. Example command:
##             ln -s /usr/local/bin/wish  /usr/bin/wish
## Then all the Tk scripts that start with /usr/bin/wish on the top line
## of the script will work.
## Tk SCRIPT NAME:   shofil.tk
## PURPOSE:  Provides a GUI FOR SCROLLING/SEARCHING/PRINTING contents of
##           a text file --- or even binary files, although most characters
##           in binary files will show as a space or null --- and as
##           'garbage' characters.
##
##  UNIQUE-INNOVATIVE FEATURE:
##  Includes a 'Show-All-Matches' (plus or minus N lines around the matches)
##  EXTRACT option. Quite handy in searching large log and list and 
##  source code files.
##
##  This is an especially handy tool for system administrators and application
##  supporters/developers.
##
## ----------------------------------------------------------------------
##
##  Note: 'shofil.tk' is A REPLACEMENT FOR OLD TEXT-TERMINAL VIEWERS.
##
##        This 'shofil' utility functions as an X-windows version of
##        the show-text-in-shell-window Unix utility 'pg' -- or its
##        more-or-less-capable cousins 'more', 'less', and 'cat'.
##
##        In other words, 'shofil.tk' is meant to provide a more modern,
##        GUI-interface method of presenting a text file to a user for
##        browsing -- more modern than the old utilities like 'pg' or 'less'
##        or a "teletype" editor like 'view' ('vi' with the readonly flag set).
##
##        This is a view-only (i.e. read-only) utility, which can be helpful
##        when you are looking at large/important files and you do not want
##        to accidentally change their contents.
##
##        Even GUI-style editors like 'nedit', have the disadvantage that the
##        user can turn the read-only switch off.  And many GUI editors,
##        like 'gedit' or 'kwrite' or 'kate' do not even offer a read-only
##        switch.
##
##        Furthermore NONE of these editors --- 'nedit', 'gedit', 'kwrite',
##        'kate' --- have the 'Show-All-Matches'  EXTRACT capability.
##
##        This GUI is nicer than 'pg' or 'less' for paging & searching.
##        'shofil.tk' allows the user to use a scrollbar for paging.
##        And, it has a 'Search(again)' button and entry field to support
##        quick searches for strings in the file.
##
##       The user can reset the starting line of the search-forward
##       feature at any time by scrolling to a text area and clicking
##       mouse-button1 on the character location to re-start the search
##       from there.
##
##       Regular-expression-search has been implemented. --- for the 
##       'Search (again)' function, but not for the 'ShowAllMatches' function.
##
##       This utility can be used in shell scripts and in Tcl-Tk scripts
##       --- either by calling 'shofil.tk' directly, or by calling a 
##       'wrapper' script named 'xpg'.
##
## CALLED BY: 
##
## This 'shofil.tk' Tcl-Tk script is called by the 'xpg' wrapper script
## OR
## 'shofil.tk' can be called in shell and Tk scripts.
##
## For example, the 'shofil.tk' utility may be used to show a help-text
## file --- from within a shell script or a Tk script.
## INPUTS:  SHOFILENAME environment variable or a (fully-qualified)
##          text-file name as an argument to the shofil.tk command.
##
## OUTPUT:  1) scrollable/pageable text-in-a-GUI display,  AND
##          2) optional popups of ShowAllMatches EXTRACT lines
##             in a separate 'shofil.tk' spawned-off window,  AND
##          3) optional printout via a print command or script.
## 
## CALL FORMAT:
##              SHOFILENAME="a-file-name",  like "$DIRapp/help/myApp.hlp"
##              export SHOFILENAME
##              $DIRxpg/shofil.tk
##        or
##              $DIRxpg/shofil.tk <file-name>
##
##  For an example call, see the 'Help' button command in this script,
##  because this shofil.tk script uses itself to show its help.
## 'CANONICAL' STRUCTURE OF THIS CODE:
##
##  0) Get input (filename) and set general window & widget parms (name,
##     position-size,color-scheme,fonts,etc.).
##  1a) Define ALL frames and sub-frames.
##  1b) Pack all the frames and sub-frames
##  2) Define & pack all widgets, frame by frame.
##  2) Define key/mouse action BINDINGS, if any.
##  3) Define PROCS, if any.
##  4) ADDITIONAL GUI INITIALIZATION, if needed (typically with one 
##                or two of the now-defined procs)
##
## In more detail,for this particular script:
##
##  1a) Define ALL frames:
## 
##    - 'fRtopbar'   (to contain exit, help, print buttons & filename label)
##    - 'fRsearch'   (to contain search button & string entry field)
##    - 'fRothropts' (a normally hidden frame, to contain other options)
##    - 'fRmain'     (to contain text area)
##
##  1b) Pack frames in appropriate groups to get proper behavior
##      of widgets in window expansion.
##
##  2) Define & pack all widgets in the frames -- basically going through
##      frames in top-to-bottom, left-to-right order.
##
##     - In '.fRtopbar':   some button widgets and a label widget
##     - In '.fRsearch':   1 label,button,entry widgets 
##     - In '.fRothropts': 1 label,checkbutton, etc. widgets
##     - In '.fRmain':     1 'text' widget with scrollbars
##
##  3) In the BINDINGS section:
##
##       - .fRsearch.entSTR <Return> 
##                   For an Enter key press in Search entry field,
##                   runs the 'search4string' proc.
##       - .fRmain.text   <ButtonRelease-1>
##                   For a Button1 click (& release) in the text area,
##                   the 'insert-cursor' location is reset in var, cursorLOC.
##       - .fRmain.text   <ButtonRelease-2> 
##                   Pops up a msg showing line.col nums of cursorLOC.
##       - .fRmain.text   <ButtonRelease-3>
##                   Selects-&-highlights a string -- to whitespace
##                   on either side of the chracter 'tapped'.
##                   I.e. mouse-button-3 click anywhere on a word and
##                   the word is high-lighted. May facilitate copy-paste.
##
##       Also special key-bindings for <Home> and <End> keys.
##
##
##  4) In the PROCS section:
##
##       - 'search4string'          for '.fRsearch.entSTR <Return>' binding
##                                  and for 'Search (again)' button.
##
##       - 'all_matches2string'     for 'Show All Matches' button.
##
##     and other procs, like
##
##       - 'popup_msg'              used to popup msg, from other procs.
##
##       - 'getset_palette_color'   gets r255,g255,b255 via the FE Color Selector GUI
##                                  with 3 slider bars,
##                                  then calls 'tk_setPalette' and 'set_text_colors'.
##                                  Called by 'ChgColor' button.
##
##       - 'set_text_colors'        sets brighter text area color and colors of two
##                                  search hilite vars, from r255,g255,b255 vars.
##
##       - 'toggle_side'            toggles y-scrollbar to left/right side of text.
##
##       - 'set_font'               starts up font-selector GUI, to chg the text font.
##
##       - 'toggle_othropts'        shows a 'toolbar' of 'other' options.
##
##       - 'selectchar2whitespace'    for  '.fRmain.text <ButtonRelease-3>' bind
##  
##       - 'readfile2textwidget'  loads text widget --- includes a technique
##                                for handling huge text files --- prompting
##                                whether to continue loading. Reads in blocks
##                                several kilobytes long. (RETIRED)
##
##       - 'getfile2textwidget'   an alternative to 'readfile2textwidget'. Uses
##                                'gets' instead of 'read'. Reads line-by-line,
##                                i.e. to a linefeed character for each line.
##                                Truncates long lines to about a kilobyte long,
##                                thus allowing for avoiding problems that might
##                                occur with loading extremely long lines into
##                                the text widget.
##
##                                Like 'readfile2textwidget', 'getfile2textwidget'
##                                includes a technique for handling input files with
##                                a huge number of records --- prompting
##                                whether to continue loading.
##
##       - 'finalize_textwidget'    called by the 'readfile' procs to display number
##                                  of lines read and disable the text widget.
##
##       - 'popup_msg_3opts'        pops up a Go/Stop/Exit message if huge number of
##                                  records are read from the input file
##
##  5) Additional GUI initialization.
##     (Runs 'set_text_colors' & 'getfile2textwidget'.)
##
## NOTE ON THE 'readfile' AND 'getfile' PROCS:
##
##     The 'readfile2textwidget' block reader (which has no checking for
##     location of line-feeds) and limits the number of characters per read
##     could be used in place of 'getfile2textwidget' --- if there were
##     some performance benefit.
##
##     Surprisingly (to me), 'getfile2textwidget' seems to be just as
##     fast as the block-reader 'readfile2textwidget'. (Probably because
##     underneath it all, the 'gets' reader has to read by blocks as well.)
##
##     The 'readfile2textwidget' routine was based on Ousterhout's
##     example of loading a text widget by using 'read' to read
##     blocks of a file ---
##     page 216, Chap 19.5 of Ousterhout book "Tcl & the Tk Toolkit".
##
##     But I changed to use a 'gets' read procedure, rather than a
##     block read, and clipped the text-line retrieved at about 1500
##     characters --- to avoid 'line too long' crashes
##     when horizontal-scrolling to view long text lines.
##
## NOTE: You can see only the non-commented, executable lines of this
##       script by using the command
##          egrep -v '^ *##|^ *# |^ *$' shofil.tk
##   or 
##          grep -v '^ *##' shofil.tk | grep -v '^ *# ' | grep -v '^ *$'
##
##       to eliminate (most of or all) the comment lines.
## DEVELOPED WITH: (Tcl 7.4)-(Tk 4.0) --- originally
##   wish>puts "$tcl_version $tk_version"
##   7.4 4.0
## Used Tcl-Tk 8.4 on Linux in 2008-2010.  Used Tcl-Tk 8.5 in 2011.
## This script was converted to 'stand-alone' form from the 'FE xpg' system,
## as provided at www.freedomenv.com.
## The FE subsystems are copyright 2006+ by Blaise Montandon
## MAINTENANCE HISTORY of 'stand-alone' shofil.tk:
## Written by: Blaise Montandon 2013aug05 Started 'stand-alone' conversion
##                                        from 'FE xpg'.
## Changed by: Blaise Montandon 20....... 

## Set the directory that contains this script, its auxiliary scripts,
## its help file, etc.

# set DIRxpg "$env(HOME)/apps/xpg"
set DIRxpg "."


##+#######################################################################
## Set WINDOW TITLE.
##+#######################################################################

wm title    . "'xpg' file browser --- version for wiki.tcl.tk"
wm iconname . "xpg"

##+#######################################################################
## Set WINDOW LOCATION --- and the SIZE (about 60 to 80% of screen size).
##     We randomize the location of the window (about a base setting)
##     using clock seconds to do the randomizing.
##     This is because this same script is used to show
##        - its own help
##        - shows the extract-lines from the SAM (ShowAllMatches) feature
##        - possibly up to 8 text files, via the 'xpg' wrapper script.
##     So we do not want the 'shofil.tk' GUI to always lie exactly
##     on top of itself.          
##+#######################################################################

## A 'base' window location.

set WINlocx 80
set WINlocy 80

# set WINlocx 15
# set WINlocy 30


## Get the current time and extract the seconds.

set secs [clock format [clock seconds] -format "%S"]

## FOR TESTING:
#  set secs "00"

## In Tcl, leading zero indicates octal number. Causes err msg:
## 'expected integer but got "08" (looks like invalid octal number)'
## from 'set adjY' statement below. So we strip off leading zero(s).

set secs [string trimleft $secs 0]
if { "$secs" == "" } {
   set secs 0
}

## Use current seconds to adjust the location.

set adjX $secs
set adjY [expr {60 - $secs}]
set WINlocx [expr {$WINlocx + $adjX}]
set WINlocy [expr {$WINlocy + $adjY}]

## Set initial window width & height, based on screen width & height.

set WINsizex [ expr {int(0.7 * [winfo screenwidth  .])} ]
set WINsizey [ expr {int(0.8 * [winfo screenheight .])} ]


## We allow for setting the window INIT-POSITION and INIT-SIZE via
## environment vars, because some uses of this GUI may have their
## own requirements for initial location and initial size.

catch { set WINlocx "$env(WINLOCX)" }
catch { set WINlocy "$env(WINLOCY)" }
catch { set WINsizex "$env(WINSIZEX)" }
catch { set WINsizey "$env(WINSIZEY)" }

## Now set both the window location and size.

set WINgeom ""
append WINgeom $WINsizex "x" $WINsizey "+" $WINlocx "+" $WINlocy
wm geometry . $WINgeom


##+######################################################
## Set the COLOR SCHEME for the window and its widgets.
##+######################################################

## These r255,g255,b255 vars will be used in a 'set_text_color'
## proc to set a slightly different shade for the text widget 
## and to set a couple of search-string 'hilite' colors,
## for successive search passes on the text widget (to help
## make clear when wrap-around of the search has occurred).
## These vars are used here to set the 'palette' of the window.

if {0} {
## Try gray.
set r255 210
set g255 210
set b255 210
} else {
## OR try something else.
set r255 110
set g255 110
set b255 255
}

set hexPALETTE [format "#%02X%02X%02X" $r255 $g255 $b255]

tk_setPalette "$hexPALETTE"


## Set consistent background colors for various widgets.

set listboxBKGD "#f0f0f0"
set entryBKGD   "#f0f0f0"
set textBKGD    "#f0f0f0"
set chkbuttBKGD "#cccccc"
set radbuttBKGD "#cccccc"


##+#######################################################################
## SET FONT VARS for use in the 'font create' statements below.
##+#######################################################################

set guiFONTsize 14
set guiFONT_SMALLsize 12

## For VARIABLE WIDTH:

set FONT_varwidth \
   " -family {comic sans ms} -size -$guiFONTsize -weight bold -slant roman "

set FONT_SMALL_varwidth \
   " -family {comic sans ms} -size -$guiFONT_SMALLsize -weight normal -slant roman "

## Some other possible (similar) variable width fonts:
##  Arial
##  Bitstream Vera Sans
##  DejaVu Sans
##  Droid Sans
##  FreeSans
##  Liberation Sans
##  Nimbus Sans L
##  Trebuchet MS
##  Verdana


## For FIXED WIDTH:

set FONT_fixedwidth \
   " -family {dejavu sans mono} -size -$guiFONTsize -weight bold -slant roman "

set FONT_SMALL_fixedwidth \
   " -family {dejavu sans mono} -size -$guiFONT_SMALLsize -weight normal -slant roman "

## Some other possible fixed width fonts (esp. on Linux):
##  Andale Mono
##  Bitstream Vera Sans Mono
##  Courier 10 Pitch
##  DejaVu Sans Mono
##  Droid Sans Mono
##  FreeMono
##  Nimbus Mono L
##  TlwgMono


##+#####################################################################
## DEFINE (temporary) FONT-NAMES using 'font create'.
## The font names are to be used in '-font' widget specs below ---
##+#####################################################################

eval font create fontTEMP_button  $FONT_varwidth
eval font create fontTEMP_label   $FONT_varwidth

eval font create fontTEMP_entry   $FONT_fixedwidth
eval font create fontTEMP_listbox $FONT_fixedwidth
eval font create fontTEMP_text    $FONT_fixedwidth
# eval font create fontTEMP_msg     $FONT_fixedwidth


eval font create fontTEMP_SMALL_button  $FONT_SMALL_varwidth
eval font create fontTEMP_SMALL_label   $FONT_SMALL_varwidth

eval font create fontTEMP_SMALL_entry   $FONT_SMALL_fixedwidth
eval font create fontTEMP_SMALL_listbox $FONT_SMALL_fixedwidth
eval font create fontTEMP_SMALL_text    $FONT_SMALL_fixedwidth
# eval font create fontTEMP_SMALL_msg     $FONT_SMALL_fixedwidth


##+#######################################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. padx,pady for Buttons)
##+#######################################################################

## For BUTTON widgets:
   
set fePADY_button 0
set fePADX_button 0
set feBDwidth_button 2


## For LABEL widgets:
   
set fePADY_label 0
set fePADX_label 0
set feBDwidth_label 2


## For  ENTRY  widgets:

set feBDwidth_entry 2


## For LISTBOX widgets:

set feBDwidth_listbox 2


## For TEXT and MESSAGE widgets:

set feBDwidth_text 2

# set feBDwidth_msg 2


## For CHECKBUTTON widgets:

# set feRELIEF_checkbutton raised
  set feRELIEF_checkbutton flat
   
set fePADY_chkbutt 0
set fePADX_chkbutt 0
set feBDwidth_chkbutt 0


## SET THE WINDOW MIN WIDTH AND HEIGHT --- MIN-SIZE (in pixels):
## Ordinarily, we set min-WIDTH such that the user can 'squeeze' the window
## horizontally, somewhat --- but not so much that important widgets
## disappear.
##
## And, for the 'shofil.tk' GUI, we set the min-HEIGHT to keep at least
## a few lines showing in the text widget.
## For WIDTH, allow for the minwidth of the '.fRtopbar' frame:
##            at least 3 buttons --- Exit,Help,Print
##            and part of the Label(for file,dir names)
##
## For HEIGHT, allow for
##             1 char high for the widgets in the '.fRtopbar' frame,
##             1 char high for the widgets in the '.fRsearch' frame,
##             1 char high for the widgets in the '.fRothropts' frame,
##        and  at least 24 pixels high for the text widget of 'fRmain'.

set minWinWidthPx [font measure fontTEMP_varwidth \
      "Exit  Help  Print  File: wwwwwwwwwwww"]

## Add some pixels to account for right-left-side window decoration
## (about 8 pixels), about 4 x 4 pixels/widget for borders/padding for
## 4 widgets --- 3 buttons and a label. 8 + 16 = 24 pixels.

set minWinWidthPx [expr {24 + $minWinWidthPx}]

## NOTE: We may want to be able to shrink the text rectangle quite a bit
## in the x-direction --- so we may not want to make the min-width very wide.
## So one could use a smaller min-width than this, if one wants to be
## able to make the window very narrow.


## MIN HEIGHT --- for the 4 frames --- allow:
##    1 char  high for 'fRtopbar'
##    1 char  high for 'fRsearch'
##    1 char  high for 'fRothropts'
##   24 pixels high for 'fRmain'

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {24 +  (3 * $CharHeightPx)}]

## Add about 28 pixels for top-bottom window decoration,
## about 2+2 pixels for each of the 4 stacked frames and their
## widgets (their borders/padding). 28 + 16 = 44.

set minWinHeightPx [expr {$minWinHeightPx + 44}]

## FOR TESTING:
#   puts "minWinWidthPx = $minWinWidthPx"
#   puts "minWinHeightPx = $minWinHeightPx"

wm minsize . $minWinWidthPx $minWinHeightPx


## An alternative to minsize:
## Freeze the window size at the initial pack size, with
##   wm resizable . 0 0
## BUT ...
## We allow the window to be resizable and we pack frame 'fRmain' with
## '-fill both -expand 1' so that the text widget can be enlarged by
## enlarging the window.


##+####################################################################
## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI.
##     NOTE: This can aid INTERNATIONALIZATION. This array can
##           be set according to a nation/region parameter.
##+####################################################################

## if { "$VARlocale" == "en"}

## For the 'fRtopbar' frame:

set aRtext(buttonEXIT)   "Exit"
set aRtext(buttonCLEAR)  "ClearStr"
set aRtext(buttonYBAR)   "<-Ybar->"
set aRtext(buttonOTHER)  "OtherOpts"
set aRtext(buttonHELP)   "Help"
set aRtext(buttonPRINT)  "Print->"


## For the 'fRsearch' frame:

set aRtext(buttonSEARCH)  "Search(again)"
set aRtext(labelSEARCH)   "for string:"
set aRtext(buttonSAM)     "ShowAllMatches = SAM"
set aRtext(labelPLUSMINUS)  "+ / -"
set aRtext(labelLINES)      "Lines"

## For the 'fRothropts' frame:

set aRtext(labelSRCHOPTS) "'Search(again)'opts:"
set aRtext(chkbuttBACK)   "Backward"
set aRtext(chkbuttCASE)   "CaseSense"
set aRtext(chkbuttREGEXP) "RegExp"

set aRtext(labelSAMOPTS) "SAMopts:"
set aRtext(chkbuttNOT)   "NOT"

set aRtext(buttonCOLOR)   "WinColor"
set aRtext(buttonFONT)    "TxtFont"

## END OF  if { "$VARlocale" == "en"}


## GET THE FILENAME TO SHOW/PRINT -- by
##      1) PROCESSING COMMAND LINE ARGUMENTS (first argument)
##   OR
##      2) GETTING CONTENTS OF ENVIRONMENT VARIABLE 'SHOFILENAME'.

set argc [llength $argv]

set FILEname ""

if {$argc == 0} {
   catch { set FILEname "$env(SHOFILENAME)" }   
} else {
   set FILEname [lindex $argv 0]
}



## IF $FILEname WAS NOT LOADED, DISPLAY ERROR AND EXIT. (uses tk_dialog)

if { "$FILEname" == "" } {

   ###########################################################
   ## $argv0 contains the name of this script --- 'shofil.tk'.
   ###########################################################

   set message "No filename was passed to $argv0

--- on command-line --- or in env-var SHOFILENAME

--- or by drag-and-drop onto a desktop icon for $argv0."

   option add *Dialog.msg.wrapLength 550

   tk_dialog .xxx "Input Err" "$message" warning 0 Close

   ##############################################################
   ## 'popup_msg' proc is not defined at this point.  This fails.
   ## Could move this check to the bottom of this script.
   ##############################################################
   # popup_msg "$message"

   exit
}


## CHECK THAT FILE EXISTS. (uses tk_dialog)

if { ![file exists $FILEname] } then {

   option add *Dialog.msg.wrapLength 400

   tk_dialog .xxx "Note" \
             "File $FILEname  not found." \
              warning 0 Close

   ##############################################################
   ## 'popup_msg' proc is not defined at this point.  This fails.
   ## Could move this check to the bottom of this script.
   ##############################################################
   # popup_msg "File $FILEname  not found."

   exit
} 


##  DEFINE *ALL* FRAMES:
##    - 'fRtopbar'   (to contain cancel & print buttons & filename label)
##    - 'fRsearch'   (to contain search button & string entry field)
##    - 'fRothropts' (a normally hidden frame, to contain other options)
##    - 'fRmain'     (to contain text area)

# set BDwidth_frame 2
# set RELIEF_frame raised

  set BDwidth_frame 0
  set RELIEF_frame flat

frame .fRtopbar    -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRsearch    -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRothropts  -relief raised         -borderwidth 2

frame .fRmain      -relief $RELIEF_frame  -borderwidth $BDwidth_frame


## PACK *ALL* FRAMES: 'fRtopbar' 'fRsearch' 'fRmain'
## --- except the normally hidden frame, 'fRothropts'.

pack .fRtopbar \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0\
   -pady 2

pack .fRsearch \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

## DO NOT USE '-expand 1'; want buttons to stay left.  


## *DEFER* PACK of the other-opts frame.
## Its packing is handled below in the 'toggle_otheropts' proc.

set OTHRoptsSHOW "NO"

# pack .fRothropts \
#    -side top \
#    -anchor nw \
#    -fill x \
#    -expand 0


## PACK the frame for the main text area.

pack .fRmain \
   -side bottom \
   -anchor sw \
   -fill both \
   -expand 1

# WAS   -side left \


##  START DEFINING & PACKING WIDGETS IN ALL FRAMES --
##  basically from top-to-bottom, left-to-right.

## IN 'fRtopbar' FRAME --
## DEFINE BUTTON WIDGETS --- 'Exit' 'Clear' '<-Bar->' 'Help' & 'Print' 
## and a filename TEXT WIDGET.
## THEN PACK THEM.
## NOTE:
## FILEname was set at the top of this script -- from arg OR from env-var.

button .fRtopbar.buttExit \
   -text "$aRtext(buttonEXIT)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {exit}

## FOR TESTING: (of font setting)
#  if { "$TEST_MODE" == "YES" } {
#     puts "Defined the 'Quit' button."
#     puts [ .fRtopbar.buttExit cget -font ]
#     puts [font actual [ .fRtopbar.buttExit cget -font ] ]
#  }


button .fRtopbar.buttClear \
   -text "$aRtext(buttonCLEAR)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {clear_search_string}


button .fRtopbar.buttSide \
   -text "$aRtext(buttonYBAR)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {toggle_side}


button .fRtopbar.buttOpts \
   -text "$aRtext(buttonOTHER)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {toggle_othropts}


button .fRtopbar.buttHelp \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command "eval exec $argv0 $DIRxpg/shofil.hlp &"

## The $argv0 says use myself (shofil.tk) to show the help.
## COULD BE
##      -command  "eval exec $DIRxpg/shofil.tk \
##      $DIRxpg/shofil.hlp &"


## Set the print command to be used by the 'Print' button.
## NOTE: The 'lpr' and 'lp' commands do not give much control
##       over font-sizes, font-styles, page-layout, etc.
# set fePRINTcmd "/usr/bin/lpr -P lp1 -h"
# set fePRINTcmd "/usr/bin/lp -d lp1 -o cpi=12 -o lpi=8 -o page-left=72"

## Linux GUI print commands:

# set fePRINTcmd "/usr/bin/kprinter"
# set fePRINTcmd "/usr/bin/hp-print"
  set fePRINTcmd "/usr/bin/cupsdoprint -P lp1 -H localhost:631"

catch { set fePRINTcmd "$env(FE_PRINT_CMD)" }

button .fRtopbar.buttPrint \
   -text "$aRtext(buttonPRINT)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command "eval exec $fePRINTcmd $FILEname &"


## To show the input filename,
## we use a 'text' widget rather than this 'label'
## widget -- so that the filename is cut-able &
## paste-able.
## label .fRtopbar.labFilename \
##    -text "File: $FILEname" \
##    -font fontTEMP_label \
##    -justify left \
##    -relief flat -borderwidth 2 \
##    -anchor w

text .fRtopbar.textFilename \
   -font fontTEMP_label \
   -height 1 \
   -relief flat \
   -borderwidth 2 \
   -wrap none

## Split the filename into its basename and dirname,
## and insert the two names in the text widget.

set FILEdir  [file dirname $FILEname]
set FILEbase [file tail    $FILEname]

if { "$FILEdir" == "." } { set FILEdir [ pwd ] }

set FILEstr  "File: $FILEbase  Dir: $FILEdir"

.fRtopbar.textFilename insert end "$FILEstr"


## Set the length of the text widget --- max 110 chars.

set textlen [string length "$FILEstr"]

# set textlen [ expr $textlen + 2 ]

if { $textlen > 100 } {set textlen 110}

.fRtopbar.textFilename configure -width $textlen

## Disable the text widget --- so the text is protected
## from alteration in the GUI.

.fRtopbar.textFilename configure -state disabled


## PACK the widgets in the 'fRtopbar' frame.

pack .fRtopbar.buttExit \
     .fRtopbar.buttHelp \
     .fRtopbar.buttSide \
     .fRtopbar.buttClear \
     .fRtopbar.buttOpts \
     .fRtopbar.buttPrint \
     .fRtopbar.textFilename \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


## IN THE 'fRsearch' frame -- DEFINE 1 BUTTON, 1 LABEL,
## and 1 ENTRY WIDGET.   THEN PACK THEM.

button .fRsearch.buttSEARCH \
   -text "$aRtext(buttonSEARCH)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -bd $feBDwidth_button \
   -command {search4string}

label .fRsearch.labSTR \
   -text "$aRtext(labelSEARCH)" \
   -font fontTEMP_SMALL_label \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $feBDwidth_button


set HOLD_cleared_string ""

entry .fRsearch.entSTR \
   -width 10 \
   -font fontTEMP_entry \
   -relief sunken \
   -bd $feBDwidth_button \
   -textvariable SEARCHstring


## PREP THE 'AllMatches' WIDGETS --
## Button, Label, & MenuButton.

button .fRsearch.buttGETmatches \
   -text "$aRtext(buttonSAM)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -bd $feBDwidth_button \
   -command {all_matches2string}

label .fRsearch.labPLUSMINUS \
   -text "$aRtext(labelPLUSMINUS)" \
   -font fontTEMP_SMALL_label \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $feBDwidth_button


# set PlusMinusLines 3
set PlusMinusLines 0

tk_optionMenu .fRsearch.optbuttPLUSMINUS PlusMinusLines \
   0 1 2 3 4 5 6 8 10 15 25

.fRsearch.optbuttPLUSMINUS configure  -font fontTEMP_SMALL_button
.fRsearch.optbuttPLUSMINUS configure  -pady $fePADY_button

label .fRsearch.labLINES \
   -text "$aRtext(labelLINES) " \
   -font fontTEMP_SMALL_label \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $feBDwidth_button


## PACK THE 'fRsearch' FRAME WIDGETS.

pack .fRsearch.buttSEARCH \
     .fRsearch.labSTR \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRsearch.entSTR \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRsearch.buttGETmatches \
     .fRsearch.labPLUSMINUS \
     .fRsearch.optbuttPLUSMINUS \
     .fRsearch.labLINES \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


## IN THE 'fRothropts' frame -- DEFINE 1 BUTTON, 1 LABEL, AND
## 3 CHECKBUTTON WIDGETS --- and a few more buttons to the right.
## THEN PACK THEM.

label .fRothropts.labSRCHOPTS \
   -text "$aRtext(labelSRCHOPTS)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $feBDwidth_button

set SRCHdirection0or1 0
set SRCHdirection0or1PREV 0

checkbutton .fRothropts.chkbuttDIRECTION \
   -text "$aRtext(chkbuttBACK)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_chkbutt \
   -pady $fePADY_chkbutt \
   -bd $feBDwidth_chkbutt \
   -variable SRCHdirection0or1 \
   -selectcolor "$chkbuttBKGD" \
   -relief $feRELIEF_checkbutton

set CASEsense0or1 0

checkbutton .fRothropts.chkbuttCASE \
   -text "$aRtext(chkbuttCASE)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_chkbutt \
   -pady $fePADY_chkbutt \
   -bd $feBDwidth_chkbutt \
   -variable CASEsense0or1 \
   -selectcolor "$chkbuttBKGD" \
   -relief $feRELIEF_checkbutton

set MATCHtype0or1 0

checkbutton .fRothropts.chkbuttMATCH \
   -text "$aRtext(chkbuttREGEXP)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_chkbutt \
   -pady $fePADY_chkbutt \
   -bd $feBDwidth_chkbutt \
   -variable MATCHtype0or1 \
   -selectcolor "$chkbuttBKGD" \
   -relief $feRELIEF_checkbutton


## PACK THE 'Search-Opts' WIDGETS in the 'fRothropts' FRAME
## --- on the left side.

pack .fRothropts.labSRCHOPTS \
     .fRothropts.chkbuttDIRECTION \
     .fRothropts.chkbuttCASE \
     .fRothropts.chkbuttMATCH \
   -side left \
   -anchor w \
   -fill x \
   -expand 0


## END   OF 'Search' opt buttons.
## START OF 'SAM' (ShowAllMatches) opt buttons.

frame .fRothropts.fRspacer1 \
   -relief flat \
   -borderwidth 0 \
   -width 10

label .fRothropts.labSAMOPTS \
   -text "$aRtext(labelSAMOPTS)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $feBDwidth_button

set SAMCASEsense0or1 0

checkbutton .fRothropts.chkbuttSAMCASE \
   -text "$aRtext(chkbuttCASE)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_chkbutt \
   -pady $fePADY_chkbutt \
   -bd $feBDwidth_chkbutt \
   -variable SAMCASEsense0or1 \
   -selectcolor "$chkbuttBKGD" \
   -relief $feRELIEF_checkbutton 

set SAMNOT0or1 0

checkbutton .fRothropts.chkbuttSAMNOT \
   -text "$aRtext(chkbuttNOT)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_chkbutt \
   -pady $fePADY_chkbutt \
   -bd $feBDwidth_chkbutt \
   -variable SAMNOT0or1 \
   -selectcolor "$chkbuttBKGD" \
   -relief $feRELIEF_checkbutton

frame .fRothropts.fRspacer2 \
   -relief flat \
   -borderwidth 0 \
   -width 15

## PACK THE 'SAM-Opts' WIDGETS in the 'fRothropts' FRAME
## --- on the left side, with a spacer.

pack .fRothropts.fRspacer1 \
     .fRothropts.labSAMOPTS \
     .fRothropts.chkbuttSAMCASE \
     .fRothropts.chkbuttSAMNOT \
     .fRothropts.fRspacer2 \
   -side left \
   -anchor w \
   -fill x \
   -expand 0


## END   OF 'SAM' (ShowAllMatches) opt buttons.
## START OF 'MISC' opt buttons (Color,Font,EdVars).

button .fRothropts.buttcolor \
   -text "$aRtext(buttonCOLOR)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {getset_palette_color}

button .fRothropts.buttfont \
   -text "$aRtext(buttonFONT)" \
   -font fontTEMP_SMALL_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {set_font}


## PACK THE 'MISC' WIDGETS in the 'fRothropts' FRAME
## --- on the right side.

pack .fRothropts.buttfont \
     .fRothropts.buttcolor \
   -side right \
   -anchor e \
   -fill x \
   -expand 0



## IN THE 'fRmain' frame -- DEFINE 1 TEXT AND 2 SCROLLBAR WIDGETS
## THEN PACK THEM.
## The following code is based on 
## page 216, Chap 19.5 of Ousterhout
## book "Tcl & the Tk Toolkit".

## Initial colors of the text widget foreground & background
## are set at the bottom of this script, by proc 'set_text_colors'.

text .fRmain.text \
   -relief raised \
   -borderwidth 4 \
   -wrap none \
   -font fontTEMP_text \
   -yscrollcommand ".fRmain.scrolly set" \
   -xscrollcommand ".fRmain.scrollx set"


scrollbar .fRmain.scrolly \
   -orient vertical \
   -command ".fRmain.text yview"

scrollbar .fRmain.scrollx \
   -orient horizontal \
   -command ".fRmain.text xview"

## GOOD TO PACK THE SCROLLBAR BEFORE THE TEXT WIDGET.
## THE TEXT WIDGET MAY TRY TO TAKE ALL THE FRAME SPACE.

set LR_sidebar "right"

pack  .fRmain.scrolly \
   -side $LR_sidebar \
   -anchor center \
   -fill y \
   -expand 0

## DO NOT USE '-expand 1' HERE; ALLOWS Y-SCROLLBAR TO X-EXPAND --
## PUTS BLANK SPACE BETWEEN Y-SCROLLBAR & ITS LISTBOX.
                
pack .fRmain.scrollx \
   -side bottom \
   -anchor center \
   -fill x \
   -expand 0

## DO NOT USE '-expand 1' HERE; KEEPS TEXT AREA FROM Y-EXPANDING.

set LR_sidetxt "left"

pack  .fRmain.text \
   -side $LR_sidetxt \
   -anchor center \
   -fill both \
   -expand 1


## WE LOAD THE SPECIFIED $FILEname INTO THE '.fRmain.text' WIDGET BELOW,
## with a read-file proc, AFTER proc's are defined.


## INITIALIZE 'SEARCHstrPREV' and 'cursorLOCold' and
## 'selBG', for use by the 'search4string' proc.
## ( 'cursorLOCold' is commented. Not really needed now.)

set SEARCHstrPREV ""

# set cursorLOCold  1.0
set cursorLOC     1.0

## The following is obsolete. We now use two different hilite
## colors, for alternating search passes --- and set them
## according to current r255,g255,b255 colors.
##        See the procs 'set_text_colors' and 'search4string'.
##
# set selBG $search_COLOR_hilite1
# .fRmain.text  tag  configure sel -background $selBG

## Initialize the RELIEF vars to be used for search 'hit' hiliting.
set search_RELIEF_hilite1 "raised"
set search_RELIEF_hilite2 "ridge"
set selRELIEF $search_RELIEF_hilite1


##  END OF INITIAL GUI DEFINITION.


##  DEFINE BINDINGS: 
##
##  Bindings related to SEARCH-ENTRY-FIELD:    (Return-Key-binding)
##
##  - .fRsearch.entSTR <Return>    search for string
##
##
##  Bindings related to BUTTON-RELEASE IN THE TEXT AREA:
##
##  - .fRmain.text <ButtonRelease-1>  set current cursor loc var, cursorLOC
##
##  - .fRmain.text <ButtonRelease-2>  popmsg showing line.col nums of cursorLOC
##
##  - .fRmain.text <ButtonRelease-3>  select-&-highlight string -- to whitespace
##                                    on either side of the chracter 'tapped'.
##
##  Bindings related to SCROLLING-THRU-TEXT:   (via *keyboard*-bindings)
##
##         - .fRmain.text  (or .)   <Home>
##         - .fRmain.text  (or .)   <End> 
##         - .fRmain.text  (or .)   <Prior>
##         - .fRmain.text  (or .)   <Next> 
##         - .fRmain.text  (or .)   <Key-Up>
##         - .fRmain.text  (or .)   <Key-Down> 

## bind .fRsearch.entSTR <Return>

bind .fRsearch.entSTR <Return>  {search4string}


## bind .fRmain.text <ButtonRelease-1>

bind .fRmain.text <ButtonRelease-1>  {

   set cursorLOC [ .fRmain.text index insert ]

   ## FOR TESTING:
   #   puts "MB1-release cursorLOC: $cursorLOC"

   ## FOR TESTING:
   #  popup_msg "New (hidden) Cursor-Location in text
   # (line & column numbers): $cursorLOC
   # (where first-line.first-colmun is 1.0)"

}
## END OF BINDING for .fRmain.text <ButtonRelease-1>


## bind .fRmain.text <ButtonRelease-2>

bind .fRmain.text <ButtonRelease-2>  {

   ## Comment this?  Does not do anything here?
   ## Requires MB-1 poke to set insert-cursor?
   set cursorLOC [ .fRmain.text index insert ]

   ## FOR TESTING:
   #   puts "MB2-release cursorLOC: $cursorLOC"

   ######################################################################
   ## Popup msg showing line.col-numbers of current
   ## cursor location.
   ######################################################################
   # set DOTindex [ string first {.} $cursorLOC ]
   # set LINEnum [ string range $cursorLOC 0 [ expr $DOTindex - 1 ] ]
   # set COLnum  [ string range $cursorLOC [ expr $DOTindex + 1 ] end]

   popup_msg "Cursor Location (line & column numbers): $cursorLOC
(For location last set with mouse-button-1.)"

}
## END OF BINDING for .fRmain.text <ButtonRelease-2>


## bind .fRmain.text <ButtonRelease-3>

bind .fRmain.text <ButtonRelease-3>  {

   selectchar2whitespace

   ## FOR TESTING:
   #   puts "MB3-release. Performed 'selectchar2whitespace'."

}
## END OF BINDING for .fRmain.text <ButtonRelease-2>


## FOR TESTING KEY-BINDINGS:
##      SHOW ANY KEY-PRESS on .fRmain.text: 
##         - .fRmain.text   <Any-Key> 

#  bind .fRmain.text <Any-Key>  {
#
#       ## FOR TESTING:
#       puts "Key pressed: %K"
#       puts "cursorLOC: $cursorLOC"
#
#  }
# ## END OF BINDING for .fRmain.text <Any-Key>


## FOR TESTING: Show default bindings with 'bindtags': 
## SHOWS:   BINDtags: .fRmain.text Text . all
#
# set BINDtags [ bindtags .fRmain.text ] 
# puts "BINDtags: $BINDtags"


## WE COULD:    OVER-RIDE THE Class 'Text' BINDINGS,
## WITH THE BINDINGS WE SET FOR '.fRmain.text' (OR '.').
# bindtags .fRmain.text [ list Text .fRmain.text . all ]


## FOR TESTING: Show new bindings with 'bindtags': 
## SHOWS:    BINDtags: Text .fRmain.text . all
#
# set BINDtags [ bindtags .fRmain.text ] 
# puts "BINDtags: $BINDtags"


##  <Home> BINDING: 
##         - .   <Home> 
# bind .fRmain.text <Home>

bind . <Home>  {

   .fRmain.text see 1.0

   ## FOR TESTING:
   #     puts "Key pressed: %K"

}
## END OF BINDING for .fRmain.text <Home>


##  <End> BINDING: 
##         - .   <End> 
# bind .fRmain.text <End>

bind . <End>  {

   # .fRmain.text see "[expr $TOTlines - 1].0" 
   .fRmain.text see end 

   ## FOR TESTING:
   #     puts "Key pressed: %K"

}
## END OF BINDING for .fRmain.text <End>


##  <Prior> BINDING:      (Page-Up key)
##         - .   <Prior> 
# bind .fRmain.text <Prior>

bind . <Prior>  {

   .fRmain.text yview scroll -1 page

   ## FOR TESTING:
   #     puts "Key pressed: %K"

}
## END OF BINDING for .fRmain.text <Prior>


##  <Next> BINDING:      (Page-Down key)
##         - .   <Next> 
# bind .fRmain.text <Next> 

bind . <Next>  {

   .fRmain.text yview scroll +1 page

   ## FOR TESTING:
   #     puts "Key pressed: %K"

}
## END OF BINDING for .fRmain.text <Next>


##  <Key-Up> BINDING:      (Up-arrow key)
##         - .   <Key-Up> 
# bind .fRmain.text <Key-Up>

bind . <Key-Up>  {

   .fRmain.text yview scroll -1 unit

   ## FOR TESTING:
   #     puts "Key pressed: %K"

}
## END OF BINDING for .fRmain.text <Key-Up>


##  <Key-Down> BINDING:      (Down-arrow key)
##         - .   <Key-Down> 
# bind .fRmain.text <Key-Down>

bind . <Key-Down>  {

   .fRmain.text yview scroll +1 unit

   ## FOR TESTING:
   #     puts "Key pressed: %K"

}
## END OF BINDING for .fRmain.text <Key-Down>


## DEFINE PROCEDURES:
##
## Search stuff:
##     - 'search4string'
##     - 'all_matches2string'
##     - 'clear_search_string'
##
## Message windows:
##     - 'tkerror'   (commented)
##     - 'popup_msg'
##     - 'popup_msg2'    (Alternative to popup_msg, using tk_dialog.
##                        Can simply use 'popup_msg' every place in
##                        this script and rename the 'proc' statements
##                        to activate the backup copy, like
##                              popup_msg  --> popup_msgX
##                        and   popup_msg2 --> popup_msg )
##
## Color stuff:
##     - 'toggle_color'  (retired; kept for reference)
##     - 'set_text_colors'
##     - 'getset_palette_color'
##
## Flip y-scroll bar side-to-side:
##     - 'toggle_side'
##
## Font stuff:
##     - 'toggle_font' (retired)
##     - 'set_font' (replaces 'toggle_font')
##
## Show current location in text widget:
##     - 'selectchar2whitespace'
##
## Show/Hide 'Other' frame:
##     - 'toggle_othropts'
##
## InputFile reading:
##     - 'readfile2textwidget'
##     - 'getfile2textwidget'
##     - 'popup_msg_3opts'
##     - 'finalize_textwidget'


## 'search4string' PROCEDURE
##
## PURPOSE: To locate the search-string in the text widget and
##          position the cursor there.
##
## CALLED BY: .fRsearch.entSTR <Return>  BINDING
##            and   button .fRsearch.buttSEARCH 
##
## Based on code in pages 523-528 of the book            
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".
## ---
## The code changed significantly on adding the backward search capability.           

proc search4string { } {

  global SEARCHstring SEARCHstrPREV cursorLOC TOTmatches
  global search_COLOR_hilite1 search_COLOR_hilite2 selBG
  global search_RELIEF_hilite1 search_RELIEF_hilite2 selRELIEF
  global PlusMinusLines
  global SRCHdirection0or1 CASEsense0or1 MATCHtype0or1
  global SRCHdirection0or1PREV

  ## Set an indicator of whether the match-highlight-color is
  ## changed during the current call to this proc.
  set selBGchanged "no"

  ## FOR TESTING:
  #   puts ""
  #   puts "*************************************************"
  #   puts "Entering proc 'search4string' with var values ..."
  #   puts "SRCHdirection0or1: $SRCHdirection0or1"
  #   puts "CASEsense0or1: $CASEsense0or1"
  #   puts "MATCHtype0or1: $MATCHtype0or1"
  #   puts ""
  #   puts "cursorLOC:  $cursorLOC"

   ########################################################################
   ## IF THE SEARCH-STRING IS A NEW ONE (i.e. if it changed):
   ##      1) Set cursorLOC (the 'index' for the 'search' command)
   ##         at the beginning or end of the text widget --- depending on
   ##         whether the current search-direction is forward or backward.
   ##      2) Reset TOTmatches to zero.
   ##      3) Remove the current 'sel' tags, and set the 'sel' tag
   ##         background color. (We toggle between two contrasting colors.)
   ##      4) Reset SEARCHstrPREV to the NEW SEARCHstring.          
   ########################################################################

   if { "$SEARCHstring" != "$SEARCHstrPREV" } {

      ## FOR TESTING:
      #   puts ""
      #   puts "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      #   puts "Search string has changed. Settings:"
      #   puts "SEARCHstring    : $SEARCHstring"
      #   puts "SEARCHstrPREV   : $SEARCHstrPREV"
      #   puts "TOTmatches: $TOTmatches"
      #   puts "cursorLOC :  $cursorLOC"

      ## If searching forward and the search string is changed, automatically
      ## re-position cursor in order to start search at top of file.
      if { $SRCHdirection0or1 == 0 } { set cursorLOC 1.0 }


      ## If searching backward and the search string is changed, leave
      ## cursor position at start of found string. Typically the user
      ## wants to find a different string.
      ## Example: Found a directory name indicator, now want to go
      ##          backward to find a filename with a different string.
      ## COMMENTED 31mar2008:  (This was sticking the search at the same string.)
      #if { $SRCHdirection0or1 == 1 } {
      #   set cursorLOC  [ .fRmain.text index "end - 1 char" ]
      #}

      set TOTmatches 0

      ## Change the highlight-color on the matches, since the search string
      ## has been changed.
      if { "$selBG" == "$search_COLOR_hilite1" } {
         set selBG $search_COLOR_hilite2
      } else {
         set selBG $search_COLOR_hilite1
      }
      set selBGchanged "yes"

      if { "$selRELIEF" == "$search_RELIEF_hilite1" } {
         set selRELIEF $search_RELIEF_hilite2
      } else {
         set selRELIEF $search_RELIEF_hilite1
      }

      ## Remove the current highlights and set the new highlighting color
      ## and the new highlighting relief.
      .fRmain.text  tag  remove  sel 1.0 end
      .fRmain.text  tag  configure sel -background $selBG
      .fRmain.text  tag  configure sel -borderwidth 3
      .fRmain.text  tag  configure sel -relief $selRELIEF

      ## Save the new search string for future comparison.
      set  SEARCHstrPREV  "$SEARCHstring"

      ## FOR TESTING:
      #   puts "-------------"
      #   puts "New settings:"
      #   puts "SEARCHstring    : $SEARCHstring"
      #   puts "SEARCHstrPREV   : $SEARCHstrPREV"
      #   puts "TOTmatches: $TOTmatches"
      #   puts "cursorLOC :  $cursorLOC"

   }
   ## END OF  if { "$SEARCHstring" != "$SEARCHstrPREV" }


   ########################################################################
   ## IF THE SEARCH DIRECTION HAS CHANGED:
   ##      1) Leave cursorLOC set to the current search-index location,
   ##         which, ordinarily, is the location of the last match
   ##         (end of the match string for a forward search, and
   ##          beginning of the match string for a backward search)
   ##         --- or cursorLOC is an initialized search-start location.
   ##      2) Reset TOTmatches to zero.
   ##      3) Remove the current 'sel' tags, and set the 'sel' tag
   ##         background color. (We toggle between two contrasting colors.)
   ##      4) Reset SRCHdirection0or1PREV.          
   ########################################################################

   if { $SRCHdirection0or1 != $SRCHdirection0or1PREV } {

      ## FOR TESTING:
      #   puts ""
      #   puts "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      #   puts "Search direction has changed. Settings:"
      #   puts "SRCHdirection0or1    : $SRCHdirection0or1"
      #   puts "SRCHdirection0or1PREV: $SRCHdirection0or1PREV"
      #   puts "TOTmatches: $TOTmatches"
      #   puts "cursorLOC:  $cursorLOC"

      ###################################################################
      ## WE DO *NOT* RESET 'cursorLOC' with a statement like the following.
      ## Nor do we adjust 'cursorLOC', when search-direction is changed.
      ###################################################################
      # set cursorLOC [ .fRmain.text index current ]
      # set cursorLOC [ .fRmain.text index insert ]

      set TOTmatches 0

      ## If the match-highlight-color has not been changed, change it to
      ## correspond to the search direction being changed.
      ## Change the relief at the same time.
      if { "$selBGchanged" == "no" } {
         if { "$selBG" == "$search_COLOR_hilite1" } {
            set selBG $search_COLOR_hilite2
         } else {
            set selBG $search_COLOR_hilite1
         }
         if { "$selRELIEF" == "$search_RELIEF_hilite1" } {
            set selRELIEF $search_RELIEF_hilite2
         } else {
            set selRELIEF $search_RELIEF_hilite1
         }
         set selBGchanged "no"
      }

      ## Remove current match-highlights and set the new
      ## match-highlighting color and the new relief.
      .fRmain.text  tag  remove  sel 1.0 end
      .fRmain.text  tag  configure sel -background $selBG
      .fRmain.text  tag  configure sel -borderwidth 3
      .fRmain.text  tag  configure sel -relief     $selRELIEF

      ## Save the current search-direction for comparison.
      set SRCHdirection0or1PREV $SRCHdirection0or1

      ## FOR TESTING:
      #   puts "-------------"
      #   puts "New settings:"
      #   puts "SRCHdirection0or1    : $SRCHdirection0or1"
      #   puts "SRCHdirection0or1PREV: $SRCHdirection0or1PREV"
      #   puts "TOTmatches: $TOTmatches"
      #   puts "cursorLOC:  $cursorLOC"

   }
   ## END OF   if { $SRCHdirection0or1 != $SRCHdirection0or1PREV }


   ########################################################################
   ##   IF THE SEARCH-STRING IS EMPTY:
   ##      1) pop a message,
   ##      2) set cursorLOC at the beginning of the text widget,
   ##      3) set TOTmatches to zero,          
   ##      4) return.
   ########################################################################

   if { "$SEARCHstring" == "" } {
      popup_msg "Need a search string for 'Search (again)'."
      set cursorLOC 1.0 ; set TOTmatches 0 ; return
   }


   ########################################################################
   ## SET THE '.fRmain.text search' COMMAND PARMS --- for
   ## CASESENSEparm and MATCHTYPEparm.          
   ########################################################################

   set SRCHDIRECTNparm "-forward"
   if { "$SRCHdirection0or1" == "1" } {
      set SRCHDIRECTNparm "-backward"
   }

   set CASESENSEparm "-nocase"
   if { "$CASEsense0or1" == "1" } {
      set CASESENSEparm ""
   }

   set MATCHTYPEparm "-exact"
   if { "$MATCHtype0or1" == "1" } {
      set MATCHTYPEparm "-regexp"
   }

   ## FOR TESTING:
   #   puts ""
   #   puts "Doing the '.fRmain.text search' with parms ..."
   #   puts "SRCHDIRECTNparm: $SRCHDIRECTNparm"
   #   puts "CASESENSEparm: $CASESENSEparm"
   #   puts "MATCHTYPEparm: $MATCHTYPEparm"
   #   puts "cursorLOC:     $cursorLOC"


   ########################################################################
   ## DO THE SEARCH -- from cursorLOC to
   ## - 'end' if the search is forward,
   ## or
   ## - '1.0' if the search is backward.         
   ########################################################################

   set length 0

   set cursorLOCsrchLAST $cursorLOC

   if { $SRCHdirection0or1 == 0 } {

      ## FOR TESTING:
      #   puts ""
      #   puts "Doing the FORWARD search."

      set cursorLOC [eval .fRmain.text search \
         $SRCHDIRECTNparm $CASESENSEparm $MATCHTYPEparm \
         -count length -- \"$SEARCHstring\" \
         $cursorLOC end ]

         # $cursorLOC ]

   } else {

      ## FOR TESTING:
      #   puts ""
      #   puts "Doing the BACKWARD search."

      set cursorLOC [eval .fRmain.text search \
         $SRCHDIRECTNparm $CASESENSEparm $MATCHTYPEparm \
         -count length -- \"$SEARCHstring\" \
         $cursorLOC 1.0 ]

      #  $cursorLOC ]  ## cycles around
      #  $cursorLOC 1.0  ]  ## works, except when $cursorLOC is 'end'
      #  1.0 $cursorLOC  ]  ## does not work
   }

   ## FOR TESTING:
   #  puts "==========================="
   #  puts "In the 'search4string' proc,"
   #  puts "right AFTER the 'search' command ..."
   #  puts ""
   #  puts "SEARCHstrPREV: $SEARCHstrPREV"
   #  puts "SEARCHstring : $SEARCHstring"
   #  puts "cursorLOC    : $cursorLOC"
   #  puts "cursorLOCsrchLAST : $cursorLOCsrchLAST"


   ########################################################################
   ## If the 'search' fails (yields a null new-cursor-location):
   ##    1) Popup a message.
   ##       (If TOTmatches not zero, show the count in the msg.)
   ##    2) Leave cursorLOC var as-is. (It will be reset if there is a
   ##       change in search-string or search-direction.  See above.)
   ##    3) Reset SEARCHstrPREV to null (to trigger the 
   ##       search-match-color-change, removal of current 'sel' selections,
   ##       and other changes, above, when a new search is started).
   ##    4) Return.
   ########################################################################

   if { "$cursorLOC" == ""  } {

      if { $TOTmatches == 0  } {
         set NOMATCHmsg "No string match for string:
$SEARCHstring"
      } else {
         set NOMATCHmsg "No further string matches for string:
$SEARCHstring

Total Matches found/high-lighted: $TOTmatches"
      }

      popup_msg "$NOMATCHmsg

Search Direction:   $SRCHDIRECTNparm
Last search-start location (Line.Column): $cursorLOCsrchLAST"

      ##################################################################
      ## WE DO *NOT* RESET 'cursorLOC' (nor search-direction) with
      ## statements like the following, when no more matches are found.
      ##################################################################
      #  set cursorLOC 1.0
      #  set SRCHdirection0or1  0
      #######################################
      ## Return display to cursorLOC 1.0
      #######################################
      #   .fRmain.text   see   $cursorLOC

      set SEARCHstrPREV ""

      ##################################################################
      ## Resetting $SEARCHstrPREV to null should suffice to trigger
      ## the following 'tag remove sel' statement & change $selBG.
      ## See 'if' statements above, when search-string or search-direction
      ## change.
      ##################################################################
      #
      #  .fRmain.text  tag  remove  sel 1.0 end
      # 
      #  if { "$selBG" == "$search_COLOR_hilite1" } {
      #     set selBG $search_COLOR_hilite2
      #  } else {
      #     set selBG $search_COLOR_hilite1
      #  }

      return

   }
   ## END OF  if { "$cursorLOC" == ""  }


   ########################################################################
   ## If we got past the previous 'if' and its 'return' (i.e. if
   ## cursorLOC was NOT set to NULL by 'search', i.e. search did NOT fail),
   ## and if the '$length' is not 0 (i.e. if 'search' definitely succeeded):
   ##    1) Add the text found to the 'sel' tag.
   ##    2) Increment TOTmatches.
   ##    3) Set cursorLOC to end of current match (if search was 'forward').
   ##    4) Use 'see' to expose the current match-line to the user.
   ##       Try to have at least one to three lines showing either
   ##       BELOW the match line (if the search was 'forward') or
   ##       ABOVE the match line (if the search was 'backward').
   ##
   ##       (In other words,
   ##        the index in the 'see' line may be set differently depending
   ##        whether the search was forward or backward.)
   ########################################################################
   ## NOTE: We could probably drop the 'if { $length != 0 }' and simply
   ##       do the statements in that 'if' section.
   ########################################################################

   if { $length != 0  } {

      .fRmain.text  tag  add  sel  $cursorLOC  "$cursorLOC + $length char"

      set TOTmatches [ expr $TOTmatches + 1 ]

      if { $SRCHdirection0or1 == 0 } {

         ################################################################
         ## If search-direction is 'forward', we adjust cursorLOC to the
         ## end of the found string,
         ## for the start of the next search for the same string.
         ################################################################
         set cursorLOC  [.fRmain.text index "$cursorLOC + $length char" ]

         ################################################################
         ## FOR a 'forward' search:
         ## We use 'see' to put the line that is $PlusMinusLines
         ## (or at least one line) past the match line, in the view area.
         ################################################################
         ##  The following group of lines used to be the single line
         ##      .fRmain.text   see   $cursorLOC
         ################################################################

         set NmoreLines $PlusMinusLines

         # if { $NmoreLines < 1  } {
         #    set NmoreLines 1
         # }

         if { $NmoreLines < 3  } {
            set NmoreLines 4
         }

         .fRmain.text   see   "$cursorLOC + $NmoreLines lines"

         #######################################
         ## Make sure the match-line is showing.
         #######################################
         .fRmain.text   see   $cursorLOC

      } else {

         ################################################################
         ## If search-direction is 'backward', we leave cursorLOC unchanged,
         ## for the start of the next search for the same string.
         ################################################################
         # set cursorLOC  [.fRmain.text index "$cursorLOC - $length char" ]

         ################################################################
         ## FOR a 'backward' search:
         ## We use 'see' to put the line that is $PlusMinusLines
         ## (or at least one line) past the match line, in the view area.
         ################################################################
         ##  The following group of lines used to be the single line
         ##      .fRmain.text   see   $cursorLOC
         ################################################################

         set NminusLines $PlusMinusLines

         # if { $NminusLines < 1  } {
         #    set NminusLines 1
         # }

         if { $NminusLines < 3  } {
            set NminusLines 4
         }

         .fRmain.text   see   "$cursorLOC - $NminusLines lines"

         #######################################
         ## Make sure the match-line is showing.
         #######################################
         .fRmain.text   see   $cursorLOC

      }
      ## END OF  if { $SRCHdirection0or1 == 0 }

   }
   ## END OF  if { $length != 0  }


   ## FOR TESTING:
   #  puts "==========================="
   #  puts "In the 'search4string' proc,"
   #  puts "AFTER the failed-'search' & successful-'search' checks ..."
   #  puts ""
   #  puts "SEARCHstrPREV: $SEARCHstrPREV"
   #  puts "SEARCHstring : $SEARCHstring"
   #  puts "cursorLOC    : $cursorLOC"
   #  puts "cursorLOCsrchLAST : $cursorLOCsrchLAST"


}
## END OF 'search4string' PROCEDURE


##  'all_matches2string' PROCEDURE
## PURPOSE: Shows the user ALL the Matches to the current search string,
##          in the entire input file.
## CALLED BY: .fRsearch.buttGETmatches button command.

proc all_matches2string { } {

   global SEARCHstring FILEname PlusMinusLines DIRxpg \
          SAMCASEsense0or1 SAMNOT0or1

   if { "$SEARCHstring" == "" } {
      popup_msg "Need a search string for ShowAllMatches."
      return
   }

   set CASESENSEyesno "no"
   if { "$SAMCASEsense0or1" != "0" } {
      set CASESENSEyesno "yes"
   }

   ## OLD PLACE-HOLDER:
   # popup_msg "Matching lines, plus-or-minus N lines, will show in a window."

   if { "$SAMNOT0or1" == "0" } {

      ## FOR TESTING:
      #   puts "SAMNOT0or1: $SAMNOT0or1"
      #   puts "string N yes/no filename : '$SEARCHstring' $PlusMinusLines $CASESENSEyesno $FILEname"

      ## OLD VERSION (Failed when search string was a char like ">",
      ##  which the shell interpreted as the direction operator.):
      #
      # exec $DIRxpg/findANDshow_stringINfile_plusminusNlines \
      #        $PlusMinusLines "$SEARCHstring" $FILEname &

      ## NEW VERSION --- with single-quotes to hide string var from shell:
      ##     (Requires an 'eval' trick in the findANDshow script,
      ##      to remove the single-quotes from around the SEARCHstring.)
      exec $DIRxpg/findANDshow_stringsINfile_plusminusNlines.sh \
             '$SEARCHstring' $PlusMinusLines "$CASESENSEyesno" "$FILEname" &

      } else {

         ## FOR TESTING:
         #   puts "SAMNOT0or1: $SAMNOT0or1"
         #   puts "string yes/no filename : '$SEARCHstring' $CASESENSEyesno $FILEname"

         exec $DIRxpg/findANDshow_NOTstringsINfile.sh \
            '$SEARCHstring' "$CASESENSEyesno" "$FILEname" &
      }
      ## END OF  if { "$SAMNOT0or1" == "0" }

}
## END OF 'all_matches2string' PROCEDURE

  
##  'clear_search_string' PROCEDURE
## PURPOSE: Clears the search string field --- or restores it, if empty.
## CALLED BY: .fRsearch.buttClear button command.

proc clear_search_string { } {

   global SEARCHstring HOLD_cleared_string

   if { "$SEARCHstring" == "" } {
      set SEARCHstring  "$HOLD_cleared_string"
   } else {
      set HOLD_cleared_string  "$SEARCHstring"
      set SEARCHstring  ""
   }

}
## END OF 'clear_search_string' PROCEDURE


##  'tkerror' PROCEDURE  (Commented. We let the Tcl-Tk interpreter
##  show a nice trace.)
## PURPOSE: Let user see errors -- to report them.
## CALLED BY: Globally available.
## Base on the Eric Johnson book 'Graphical Applications with Tcl & Tk',
## page 263, Chapter 8 "Tcl Tricks & Traps: Handling Errors & Debugging".

## Method of commenting the proc without putting '#' on each line.
if { 0 } {

proc tkerror { errmsg } {

   global errorInfo errorCode w env

   set TKtest ""

   catch { set TKtest $env(TKTEST) }

   if { "$TKtest" == "YES" } {

      set msg [format "Error: %s\nResult: %s."  $errmsg  $errorCode]

   } else {

      set msg  [format "Error: %s\n"  $errmsg]

   }

   #############################################
   ## Alert user to error.
   #############################################
   ## tk_dialog .dlg "Error" $msg error 0 OK

   #############################################
   ## RETURN THE CURSOR TO A 'LEFT POINTER'. 
   #############################################
   #  . configure -cursor { left_ptr red white }

   ## return

}
## END OF 'tkerror' PROCEDURE

}
## END OF if { 0 } to comment this proc.


##  'popup_msg' PROCEDURE
## PURPOSE: Report information (a text message) to the user.
## CALLED BY: 'file_info' proc and error-check 'if' statements in various
##            procs.
## To have more control over the formatting of the message (esp.
## words per line), we use this 'toplevel-with-text-widget' window making
## method, rather than the 'tk_dialog' method  --- see 'popup_msg2' proc
## below --- from page 574 of the book by Hattie Schroeder & Mike Doyel,
## 'Interactive Web Applications with Tcl/Tk', Appendix A
## "ED, the Tcl Code Editor".

proc popup_msg  { VARtext } {

   global feFONT_text feFONT_button feBDwidth_text feBDwidth_button

   ## Use 'THIShost' and 'env(USER)' in popup wintitle.
   global THIShost env

   # bell
   bell

   #############################################
   ## SETUP 'TOP LEVEL' HELP WINDOW --- titles.
   #############################################

   set w .topmsg

   catch {destroy $w}
   toplevel  $w

   # wm title     $w "ShowFile Note, from $THIShost to $env(USER)"
   wm title     $w "ShowFile Note"
   wm iconname  $w "ShoFilNote"

   ######################################
   ## To keep the popup in front of other
   ## windows, incl. the one generated by
   ## this app, try one of the following.
   ######################################
   # raise .topmsg
   # raise .topmsg .
   ## This is said to work with the Gnome metacity wm:
   # wm group .topmsg .

   #############################################
   ## Put '.topmsg' window near upper-left corner
   ## of the application '.' window.
   #############################################
   ## Ref: p.140, Chap 4 Menus, "Graphical
   ##      Applications with Tcl & Tk",
   ##      Eric F. Johnson
   #############################################
   ## COMMENTED. Instead, we set the location 
   ## relative to the upper left of the
   ## 'shofil.tk' GUI window.
   #############################################
   ## wm geometry $w 662x646+431+202

   ## wm geometry $w +200+100
   # wm geometry $w +050+050

   ##############################################
   ## SET the location of the popup msg window
   ## relative to the upper left of the
   ## 'shofil.tk' GUI window.
   #############################################
   ## Get global x,y pos of app.
   #############################################

   set gx [winfo rootx .]
   set gy [winfo rooty .]

   ## If we were to postition at mouse cursor:
   ## Add local mouse pos.
   # set mx [expr %x + $gx]
   # set my [expr %y + $gy]

   set gx [expr $gx + 300]
   set gy [expr $gy + 120]

   wm geometry $w +${gx}+${gy}


   ###########################################
   ## SET the MINSIZE of the popup msg window.
   ###########################################

    wm minsize $w 200 60


   #####################################
   ## SETUP TEXT WIDGET for popup msg.
   #####################################

   text $w.text \
      -relief raised \
      -borderwidth $feBDwidth_text \
      -font fontTEMP_text

   pack  $w.text \
      -side top \
      -anchor center \
      -fill both \
      -expand 0

   ##  $w.text delete 1.0 end

   $w.text insert end $VARtext

   $w.text  configure -state disabled

   ###################################################
   ## SETUP 'Close' BUTTON WIDGET on popup msg window.
   ###################################################

   button $w.buttClose \
      -text "Close" \
      -font fontTEMP_button \
      -relief raised -borderwidth $feBDwidth_button \
      -command " destroy $w ; return "

   pack  $w.buttClose \
      -side top \
      -anchor center \
      -fill none \
      -expand 0

   #################################################
   ## Get VARheight & VARwidth from $VARtext --- and
   ## set the height and width of the text widget in
   ## the popup msg window.
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################

   set VARlist [ split $VARtext "\n" ]

   ## FOR TESTING:
   #  puts "VARlist: $VARlist"

   set VARheight [ llength $VARlist ]

   ## FOR TESTING:
   #  puts "VARheight: $VARheight"

   $w.text configure -height $VARheight

   #################################################
   ## To get VARwidth,
   ##    loop through the 'lines' getting length
   ##     of each; save max.
   #################################################

   set VARwidth 0

   #############################################
   ## LOOK AT EACH LINE IN THE LIST.
   #############################################
   foreach line $VARlist {

      #############################################
      ## Get the length of the line.
      #############################################
      set LINEwidth [ string length $line ]


      if { $LINEwidth > $VARwidth } {
         set VARwidth $LINEwidth
      }

   }
   ## END OF  foreach line $VARlist

   set VARwidth [ expr $VARwidth + 1 ]

   $w.text configure -width  $VARwidth

   ## FOR TESTING:
   #   puts "VARwidth: $VARwidth"

   ########################################################################
   ## NOTE: VARwidth works for an adobe-courier fixed-width font used
   ##       for the text widget ... BUT the programmer may need to be
   ##       careful that the contents of VARtext
   ##       are all countable characters by the 'string length' command.
   ########################################################################

}
## END OF 'popup_msg' PROCEDURE



##  'popup_msg2' PROCEDURE  
## PURPOSE: Report search-failure conditions to the user.
## CALLED BY: near bottom of 'search4string' proc, at "No match."
## From page 574 of the book 
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".

proc popup_msg2  { message } {

   # sound_bell

   set w .popmsg

   ######################################################################
   ## This 'destroy' does not work on the 'tk_dialog' window, because
   ## 'tk_dialog' hangs the app, waiting for a click on one of its buttons.
   ######################################################################
   # catch {destroy $w}

   #############################################
   ## After 100 milliseconds, grab' the window. 
   #############################################
   ## Not good practice. Can cause screen lockups. 
   #############################################
   #    
   #   after 100 { 
   #      grab -global $w 
   #   }    
   #############################################     

   #############################################
   ## Use 'tk_dialog' to define&pack the window. 
   #############################################

   tk_dialog $w "Note" $message warning 0 Close

   #############################################
   ## Release the 'grabbed' window. 
   #############################################     
   #   grab release $w
   #############################################     

}
# ## END OF 'popup_msg2' PROCEDURE


## 'toggle_color' PROCEDURE  (NOT USED ; maybe someday)
##
## PURPOSE: To 'switch' the foreground & background colors of
##         the text widget. 
##
## CALLED BY:  .fRtopbar.togcolor button
##

## Method to comment out the following proc without using '#' on each line.
if { 0 } {

proc toggle_color { } {

   global COLOR_fg COLOR_bg feFONT_text

   global  COLOR_palette_dark COLOR_palette_bright

   global  COLOR_textdark COLOR_bkgdbright  COLOR_textbright COLOR_bkgddark 

   ### Could use a larger font in bright-text-on-dark to improve readability.
   ### Declare FONT_LARGE_Text above in the font-defs section.
   #
   # global FONT_TextHold FONT_LARGE_Text

   if { "$COLOR_fg" == "$COLOR_textdark" } {

      set COLOR_fg $COLOR_textbright

      set COLOR_bg $COLOR_bkgddark

      tk_setPalette  $COLOR_palette_dark

      # set FONT_TextHold $FONT_LARGE_Text

   } else {

      set COLOR_fg $COLOR_textdark

      set COLOR_bg $COLOR_bkgdbright    

      tk_setPalette  $COLOR_palette_bright

      #  set FONT_TextHold $FONT_Text

   }

   .fRmain.text  configure  -fg $COLOR_fg   -bg $COLOR_bg   -font fontTEMP_text

}
## END OF 'toggle_color' PROCEDURE

}
##END OF if { 0 } to comment out this proc.


## 'set_text_colors' PROCEDURE
## PURPOSE:
##   This procedure is invoked to set a slightly brighter background color
##   for the text widget, .fRmain.text --- brighter than the color given by
##   current values in three global vars --- r255 g255 b255 ---
##   which should be the values used to set the 'palette' of the entire GUI.
##
##   This proc also sets search-hilite vars
##           set search_COLOR_hilite1
##           set search_COLOR_hilite2
##   to contrast to text-area background color.
##
##   These 2 search_COLOR vars are used in the 'search4string' proc --- to
##   high-light match strings. One color is used in a search pass, and if
##   the search is changed in some sense (direction, search string, whatever),
##   the other color is used for the next search pass. (For those who are
##   color blind, 2 'relief' vars are also used.)
##   
##     [Could also call proc 'set_foregd_color' (below) to set the
##      FOREGROUND text widget color to black or off-white depending
##      on the luminance value of the chosen text background color.
##
##      That is, we could use a different foreground-color setting technique
##      from the foreground-color setting logic used in 'tk_setPalette'.]
##
## Arguments: none
##
## CALLED BY:  proc 'getset_palette_color' below, which is invoked by
##             the 'ChgColor' button
##           
## This proc is ALSO CALLED at the bottom of this script as a GUI
## initialization procedure (for the colors in the text widget).

proc set_text_colors {} {

   global r255 g255 b255
   global search_COLOR_hilite1 search_COLOR_hilite2 selBG


   ######################################################
   ## Set text-area BACKGROUND color to slightly brighter
   ## color than the current r255,g255,b255 values --- 
   ## which should be the values used to set the GUI palette.
   ######################################################

   set CFACT 1.1

   set r255TEMP  [ expr int($CFACT * $r255) ]
   set g255TEMP  [ expr int($CFACT * $g255) ]
   set b255TEMP  [ expr int($CFACT * $b255) ]

   if { $r255TEMP > 255 } { set r255TEMP 255 }
   if { $g255TEMP > 255 } { set g255TEMP 255 }
   if { $b255TEMP > 255 } { set b255TEMP 255 }

   set COLOR_bg_hex [format "#%02X%02X%02X" $r255 $g255 $b255]

   .fRmain.text  configure -bg $COLOR_bg_hex

   ## We could change the foreground color too, in the text area, with:
   ##    .fRmain.text  configure -bg $COLOR_bg_hex  -fg $COLOR_fg_hex
   ## BUT we keep the same foreground color as last set by 'tk_setPalette'.


   ###############################################################
   ## SET 2 COLORS TO USE FOR SEARCH-STRING HIGH-LIGHTING.
   ###############################################################
   ## This code is intended to assure hilites are set different from
   ## text widget BACKGROUND color.
   ## (We also should include some logic to assure that these
   ## colors are not too close of the FOREGROUND color of the text.
   ## Probably should query the current foreground color. Someday?
   ## For now, can use 'ChgColor' button to try different colors.)
   ###############################################################

   set LUMval [ expr .299*$r255 + .587*$g255 + .114*$b255 ]

   set MIDlum 127

   if { $LUMval < $MIDlum } {
      set r1 200
      set g1 200
      set b1   0
      set r2   0
      set g2 200
      set b2 200
   } else {
      set r1 100
      set g1 100
      set b1   0
      set r2   0
      set g2 100
      set b2 100
   }

   # if { $r255 > $MIDlum } {
   #    set r1 [ expr int( 0.5 * $r255 ) ]
   #    set r2 [ expr int( 0.2 * $r255 ) ]
   # } else {
   #    set r1 [ expr int( 1.7 * $r255 ) ]
   #    set r2 [ expr int( 1.4 * $r255 ) ]
   # }

   # if { $g255 > $MIDlum } {
   #    set g1 [ expr int( 0.7 * $g255 ) ]
   #    set g2 [ expr int( 0.4 * $g255 ) ]
   # } else {
   #    set g1 [ expr int( 1.6 * $g255 ) ]
   #    set g2 [ expr int( 1.3 * $g255 ) ]
   # }

   # if { $b255 > $MIDlum } {
   #    set b1 [ expr int( 0.6 * $b255 ) ]
   #    set b2 [ expr int( 0.4 * $b255 ) ]
   # } else {
   #    set b1 [ expr int( 1.5 * $b255 ) ]
   #    set b2 [ expr int( 1.3 * $b255 ) ]
   # }

   set COLOR1 [format "#%02X%02X%02X" $r1 $g1 $b1]
   set search_COLOR_hilite1 "$COLOR1"

   # FOR TESTING:
   #  puts "COLOR1: $COLOR1 $r1 $g1 $b1"


   set COLOR2 [format "#%02X%02X%02X" $r2 $g2 $b2]
   set search_COLOR_hilite2 "$COLOR2"

   # FOR TESTING:
   #  puts "COLOR2: $COLOR2 $r2 $g2 $b2"

   ## In case we were in the middle of a search when we
   ## clicked the 'ChgColor' button to change the palette and
   ## text-area colors, we reset the 'sel' tag color to
   ## assure we have a hilite color based on the new color.

   set selBG $search_COLOR_hilite1
   .fRmain.text  tag  configure sel -background $selBG

}
## END OF 'set_text_colors' PROCEDURE



## 'set_foregd_color' PROCEDURE    (NOT USED, yet)
## PURPOSE:
##   This procedure is invoked to set a foreground color for text on
##   the text widget, .fRmain.text,  based on
##   current values in three global vars:  r255 g255 b255
##
## Arguments: none
##
## CALLED BY:  proc ?

proc set_foregd_color {} {

   ######################################################
   ## Set text FOREGROUND color to black or off-white,
   ## according to luminance of background --- the
   ## current r255,g255,b255 colors.
   ######################################################
   ##   NOTE: 'Luminance' (Y) is given by a
   ##         weighted average of RGB values,
   ##         according to the formula:
   ##    
   ##                 Y = .299*R + .587*G + .114*B
   #####################################################

   set LUMval [ expr .299*$r255 + .587*$g255 + .114*$b255 ]
   set LOWlum 90

   if { $LUMval < $LOWlum } {
      set COLOR_fg "#CCCCCC"  
   } else {
      set COLOR_fg "#000000"
   }

   ################################################################
   ## Set a somewhat different bkgd color for window heading and
   ## border (to be set by tk_Palette), based on the chosen text
   ## bkgdd color = r255,g255,b255.
   ##
   ## If the text background has high luminance,
   ## we make the heading-and-border a little darker.
   ## If the text background has low luminance,
   ## we make the heading-and-border a little brighter.
   ################################################################

   set CFACT 0.85
   set CMIN  60

   if { $LUMval > $LOWlum } {

      set r255TEMP  [ expr int($CFACT * $r255) ]
      set g255TEMP  [ expr int($CFACT * $g255) ]
      set b255TEMP  [ expr int($CFACT * $b255) ]

   } else {

      set r255TEMP  [ expr $r255 + $CMIN ]
      set g255TEMP  [ expr $g255 + $CMIN ]
      set b255TEMP  [ expr $b255 + $CMIN ]

      if { $r255TEMP > 255 } { set r255TEMP 255 }
      if { $g255TEMP > 255 } { set g255TEMP 255 }
      if { $b255TEMP > 255 } { set b255TEMP 255 }
   }

   set COLOR_fg [format "#%02X%02X%02X" $r255TEMP $g255TEMP $b255TEMP]

   .fRmain.text  configure  -fg $COLOR_fg

}
## END OF proc 'set_foregd_color'


## 'getset_palette_color' PROCEDURE
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet (r255 g255 b255)
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses 'tk_setPalette' to set the palette of the entire GUI.
##
##   Then uses 'set_text_colors' proc to set slightly different text area
##   color for the text widget, .fRmain.text --- and colors for two
##   search color hilite vars.
##
## Arguments: none
##
## CALLED BY:  .fRothropts.buttcolor  button

proc getset_palette_color {} {

   global r255 g255 b255 DIRxpg

   ## FOR TESTING:
   #    puts "r255: $r255"
   #    puts "g255: $g255"
   #    puts "b255: $b255"

   set TEMPrgb [ exec \
      $DIRxpg/sho_colorvals_via_sliders3rgb.tk \
      $r255  $g255 $b255]

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }

   ## 2010aug23 changed output of 'sho_colorvals_via_sliders3rgb.tk'.
   ## It no longer has the strings 'R255=','G255=','B255='.
   # scan $TEMPrgb "R255=%s ; G255=%s ; B255=%s" r255 g255 b255
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"
   #    puts "r255: $r255"
   #    puts "g255: $g255"
   #    puts "b255: $b255"

   eval set r255 $r255
   eval set g255 $g255
   eval set b255 $b255

   set COLOR_hex [format "#%02X%02X%02X" $r255 $g255 $b255]

   tk_setPalette  $COLOR_hex

   #########################################################################
   ## Set text area to slightly brighter color and set 2 search hilite vars
   ## from current values in r255,g255,b255.
   #########################################################################
   set_text_colors

}
## END OF 'getset_palette_color' PROCEDURE


## 'toggle_side' PROCEDURE
## 
## PURPOSE: To switch the sides of the 'sliders' & 'preview' frames 
## 
## CALLED BY:  .fRtopbar.buttSide button
## 

proc toggle_side { } {

   global LR_sidebar LR_sidetxt

   if { "$LR_sidebar" == "left" } {
      set LR_sidebar "right"
      set LR_sidetxt "left"
   } else {
      set LR_sidebar "left"
      set LR_sidetxt "right"
   }

   pack forget .fRmain.scrolly .fRmain.text

   pack  .fRmain.scrolly \
      -side $LR_sidebar \
      -anchor center \
      -fill y \
      -expand 0

   pack  .fRmain.text \
      -side $LR_sidetxt \
      -anchor center \
      -fill both \
      -expand 1

}
## END of proc 'toggle_side'



## 'set_font' PROCEDURE
## 
## PURPOSE: To show the 'select_tkFont.tk' GUI to the user so they
##          can select from all fonts known to Tcl-Tk ---
##          anti-aliasable and scalable fonts, as well as X-fonts. 
## 
## CALLED BY:  .fRtopbar.buttfont button
## 

proc set_font { } {

global DIRxpg fontTEMP_text

   # set TEST_MODE "YES"
   set TEST_MODE "NO"

   if { "$TEST_MODE" == "YES" } {
      puts "Proc: set_font : Changing text font via GUI."
   }

   set textAreaFONTparms [ font actual fontTEMP_text ]
   set newFONTspecs [ exec $DIRxpg/select_tkFont.tk $textAreaFONTparms ]

   if { "$newFONTspecs" == ""} {
      return
   }

   font delete fontTEMP_text
   eval font create fontTEMP_text $newFONTspecs
   .fRmain.text  configure  -font fontTEMP_text

   ## WAS:
   # eval font create fontTEMP $FONTspecs
   # .fRmain.text  configure  -font fontTEMP

   ## FOR TESTING:
   # font create fontTEMP -family { new century schoolbook } -size 14 \
   #              -weight bold -slant roman -underline 0 -overstrike 0
   # .fRmain.text  configure  -font fontTEMP

   if { "$TEST_MODE" == "YES" } {
      puts "Changed text font to $FONTspecs"
      puts [ .fRmain.text cget -font ]
      puts [font actual [ .fRmain.text cget -font ] ]
   }

}
##END of proc 'set_font'



## 'selectchar2whitespace' PROCEDURE
##
## PURPOSE: To highlight-select a string (like a filename containing 
##          special characters like slashes) with a single click --- 
##          instead of double-click high-lighting ending at special
##          characters to the left & right of the clicked-upon
##          character --- and instead of a triple-click high-lighting
##          the entire line.
##
##          I.e. to highlight-select a string with a single click ---
##          FROM the first white-space (space or tab) to the left of 
##          the clicked-upon character --- and TO the first white-space
##          to the right of the clicked-upon character.
##
## CALLED BY:   .fRmain.text  <ButtonRelease-3>
##
## NOTE: Currently it takes over 20 statements to lead up to the
##       'tag add sel' statement that does the highlighting-&-selection.
##       Hopefully we can find a more compact way to do this.
##
##       Unfortunately, there are 'string first' and 'string last' index
##       commands, but nothing like 'string left' and 'string right'
##       substring commands.  (Perhaps we should make two procs called
##       'string_left' and 'string_right' and use them here --- and
##       be positioned to use them as utilities elsewhere.)

proc selectchar2whitespace { } {

   # global ... (not needed?)

   set charLOC [.fRmain.text index current]

   ## FOR TESTING:
   #    puts "Set charLOC using '.fRmain.text index current' ..."
   #    puts "charLOC: $charLOC"


   ######################################################
   ## charLOC is an index of the form linenum.colnum.
   ## We break it apart into a separate linenum & colnum.
   ######################################################

   set DOTindex [string first {.} $charLOC]
   set LINEnum [string range $charLOC 0 [expr $DOTindex - 1]]
   set COLnum  [string range $charLOC [expr $DOTindex + 1] end]

   ## FOR TESTING:
   #    puts "Got charLOC at MB3 release ..."
   #    puts "breaking apart charLOC into ..."
   #    puts "DOTindex: $DOTindex"
   #    puts "LINEnum : $LINEnum"
   #    puts "COLnum  : $COLnum"


   #############################################################
   ## Put the contents of theentire line that was clicked-on
   ## in the var LINEsel.
   #############################################################

     set LINEsel [.fRmain.text get ${LINEnum}.0 ${LINEnum}.end]

   ## Alternative: (?)
   # set LINEsel [.fRmain.text get "$charLOC linestart" " $charLOC lineend" ]

   ## FOR TESTING:
   #    puts "Got the 'MB3-tapped' line in var LINEsel ..."
   #    puts "LINEsel: $LINEsel"

   #############################################################
   ## Get the string to the LEFT of the clicked-on character,
   ## and the length of that string -- in leftSTRING & leftLEN.
   #############################################################

   set LINEleft  [string range $LINEsel 0 $COLnum]

   ## FOR TESTING:
   #     puts "Here is the left part of the line,"
   #     puts "from the 'MB3-tapped' character ..."
   #     puts "LINEleft: $LINEleft"

   ##  For now, we simply look for the last space, rather than
   ##  the last whitespace (space or tab character).
   set LASTindex [string last " " $LINEleft ]

   if {$LASTindex == -1} {
      set LASTindex 0
   } else {
     set LASTindex [expr $LASTindex + 1]
   }

   ## FOR TESTING:
   #     puts "Here is the 'index' to the last space"
   #     puts "in LINEleft ..."
   #     puts "LASTindex: $LASTindex"

   set leftSTRING [string range $LINEleft $LASTindex end]

   ## FOR TESTING:
   #     puts "Here is the part of LINEleft from the space"
   #     puts "to the end of LINEleft ..."
   #     puts "leftSTRING: $leftSTRING"

   ## We may want to NOT-select&highlight quotes or left-paren on the left.
   # set leftSTRING [ string trimleft $leftSTRING "'\"(" ]

   set leftLEN [string length $leftSTRING]

   ## FOR TESTING:
   #     puts "Here is the length of leftSTRING ..."
   #     puts "leftLEN: $leftLEN"


   #############################################################
   ## Get the string to the RIGHT of the clicked-on character,
   ## and the length of that string -- in rightSTRING & rightLEN.
   #############################################################

   set LINEright  [string range $LINEsel $COLnum end]

   ## FOR TESTING:
   #     puts "Here is the right part of the line,"
   #     puts "from the 'MB3-tapped' character ..."
   #     puts "LINEright: $LINEright"

   ##  For now, we simply look for the first space, rather than
   ##  the first whitespace (space or tab character).
   set FIRSTindex [string first " " $LINEright ]

   if {$FIRSTindex == -1} {
      set FIRSTindex [string length $LINEright]
   }

   ## FOR TESTING:
   #     puts "Here is the 'index' to the first space"
   #     puts "in LINEright ..."
   #     puts "FIRSTindex: $FIRSTindex"

   set rightSTRING [string range $LINEright 0 [expr $FIRSTindex - 1] ]

   ## FOR TESTING:
   #     puts "Here is the part of LINEright from the beginning"
   #     puts "of LINEright to the first space ..."
   #     puts "rightSTRING: $rightSTRING"

   ## We may want to NOT-select&highlight quotes or right-paren on the right.
   # set righttSTRING [ string trimright $rightSTRING "'\")" ]

   set rightLEN [string length $rightSTRING]

   ## FOR TESTING:
   #     puts "Here is the length of rightSTRING ..."
   #     puts "rightLEN: $rightLEN"


   #############################################################
   ## Remove previous selections.
   #############################################################

   .fRmain.text tag remove sel 1.0 end 

   #############################################################
   ## Make selection using $charLOC, $leftLEN, $rightLEN.
   #############################################################

   .fRmain.text tag add sel \
      "$charLOC - [expr $leftLEN - 1] char" \
      "$charLOC + $rightLEN char" 

   #   "$charLOC - [expr $leftLEN - 2] char"

   ## FOR TESTING:
   #     puts "Performed 'tag add sel' in the text area ..."
   #     puts "using the variables ..."
   #     puts "charLOC   : $charLOC"
   #     puts "leftLEN   : $leftLEN"
   #     puts "rightLEN  : $rightLEN"


}
## END of proc 'selectchar2whitespace'


## 'toggle_othropts' PROCEDURE
##
## PURPOSE: To show (or hide) the widgets in the 'fRothropts' frame. 
##
## CALLED BY:  .fRtopbar.buttOpts   button
##

proc toggle_othropts { } {

   ##########################################
   #### METHOD: with 'pack forget' & 'pack'
   ##########################################

   global OTHRoptsSHOW  minWIDTH1  minHEIGHT1  minHEIGHT2
   # global MINWIDTH MINHEIGHT

   if { "$OTHRoptsSHOW" == "NO" } {
      ## THIS ROUTINE IS DONE WHEN "$OTHRoptsSHOW" == "NO" --- for example,
      ## 1st time 'toggle_othropts' is executed & odd-numbered times.
      ##
      ## This routine does 'show' frame 'fRothropts'.

      # set winX [winfo x .]
      # set winY [winfo y .]

      # set holdWIDTH [winfo width .]

      #   wm geometry . ${holdWIDTH}x${minHEIGHT2}+${winX}+$winY
      # # wm geometry . ${minWIDTH1}x${minHEIGHT2}+${winX}+$winY

      # wm minsize  . $minWIDTH1 $minHEIGHT2
      # wm minsize  . $MINWIDTH  $minHEIGHT2
      # wm minsize  . $MINWIDTH  $MINHEIGHT

      pack forget .fRtopbar
      pack forget .fRsearch
      pack forget .fRmain

      pack .fRtopbar \
         -side top \
         -anchor w \
         -fill x \
         -expand 0 \
         -pady 2

      pack .fRsearch \
         -side top \
         -anchor w \
         -fill x \
         -expand 0

      pack  .fRothropts \
         -side top \
         -anchor w \
         -fill x \
         -expand 0 

      pack .fRmain \
         -side bottom \
         -anchor w \
         -fill both \
         -expand 1

         #     -fill x \
         #     -expand 0

      set OTHRoptsSHOW "YES"

   } else {
      ## THIS ROUTINE IS DONE WHEN "$OTHRoptsSHOW" == "YES" --- which
      ## is the 2nd time 'toggle_othropts' is executed & all other
      ## even-numbered times.
      ##
      ## This routine does 'hide' frame 'fRothropts'.

      # set winX [winfo x .]
      # set winY [winfo y .]

      pack forget .fRothropts

      # set holdWIDTH [winfo width .]

      #   wm geometry . ${holdWIDTH}x${minHEIGHT1}+${winX}+$winY
      # # wm geometry . ${minWIDTH1}x${minHEIGHT1}+${winX}+$winY

      # wm minsize  . $minWIDTH1 $minHEIGHT1
      # wm minsize  . $MINWIDTH  $minHEIGHT1
      # wm minsize  . $MINWIDTH  $MINHEIGHT

      set OTHRoptsSHOW "NO"

   }
   ## END of   if { "$OTHRoptsSHOW" == "NO" }

}
## END of proc 'toggle_othropts'


##  'popup_msg_3opts' PROCEDURE 
## PURPOSE: Report information (a text message) to the user
##          and present 3 options.
## CALLED BY: 'readfile2textwidget' proc.  (below)
## To have more control over the formatting of the message (esp.
## words per line), we use this 'toplevel-text' method,
## rather than the 'tk_dialog' method from page 574 of the book
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".
## For an example of making your own dialog window, see the book by
## Eric Foster-Johnson, 'Graphical Applications with Tcl & Tk', Chapter 7,
## "Dialog Windows".  See pgs 247-250 of edition 1.

proc popup_msg_3opts  { VARtext } {

   global feFONT_text feFONT_button feBDwidth_text feBDwidth_button
   global REPLY_3opts

   ## Use 'THIShost' and 'env(USER)' in popup wintitle.
   global THIShost env

   # bell
   bell

   #####################################
   ## SETUP 'TOP LEVEL' HELP WINDOW.
   #####################################

   set w .topmsg

   catch {destroy $w}
   toplevel  $w -class Dialog

   ## Declare 'transient' if you want no window frame.
   wm transient  $w

   wm title     $w "Reading-File Options, host $THIShost user $env(USER)"
   wm iconname  $w "ReadFilOpts"

   ######################################
   ## To keep the popup in front of other
   ## windows, incl. the one generated by
   ## this app, try one of the following.
   ######################################
   # raise .topmsg
   # raise .topmsg .
   ## This is said to work with the Gnome metacity wm:
   # wm group .topmsg .

   #############################################
   ## Put '.topmsg' window near upper-left corner
   ## of the application '.' window. 
   #############################################
   ## Ref: p.140, Chap 4 Menus, "Graphical
   ##      Applications with Tcl & Tk",
   ##      Eric F. Johnson
   #############################################

   ## wm geometry $w 662x646+431+202

   ## wm geometry $w +200+300
   # wm geometry $w +050+050

   ## Get global x,y pos of app.
   set gx [winfo rootx .]
   set gy [winfo rooty .]

   ## If we were to postition at mouse cursor:
   ## Add local mouse pos.
   # set mx [expr %x + $gx]
   # set my [expr %y + $gy]

   #  set gx [expr $gx + 300]
   #  set gy [expr $gy + 120]

   wm geometry $w +${gx}+${gy}

   #############################################
   ## Set minsize of '.topmsg' window.
   #############################################

    wm minsize $w 200 60

   #############################################
   ## Make two frames, for text and for buttons.
   #############################################

   frame $w.fRmsg    -bd 2 -relief flat
   frame $w.fRbutts  -bd 2 -relief raised

   pack $w.fRmsg \
        $w.fRbutts \
      -side top \
      -anchor center \
      -fill x \
      -expand 1

   ## DO NOT USE '-expand 1'; if want buttons to stay left.  


   #####################################
   ## SETUP TEXT WIDGET.
   #####################################

   text $w.fRmsg.text \
      -relief raised \
      -borderwidth $feBDwidth_text \
      -font fontTEMP_text

   pack  $w.fRmsg.text \
      -side top \
      -anchor center \
      -fill both \
      -expand 0

   ##  $w.text delete 1.0 end

   $w.fRmsg.text insert end $VARtext

   $w.fRmsg.text  configure -state disabled

   ############################################
   ## SETUP 'More' 'Stop' 'Exit' BUTTON WIDGETS.
   ############################################

   button $w.fRbutts.buttMore \
      -text "More" \
      -font fontTEMP_button \
      -relief raised -borderwidth $feBDwidth_button \
      -command "set REPLY_3opts More ; destroy $w "

      #   -command "puts 'More' ; destroy $w ; return "

   button $w.fRbutts.buttStop \
      -text "Stop" \
      -font fontTEMP_button \
      -relief raised -borderwidth $feBDwidth_button \
      -command "set REPLY_3opts Stop ; destroy $w "

   button $w.fRbutts.buttExit \
      -text "Exit" \
      -font fontTEMP_button \
      -relief raised -borderwidth $feBDwidth_button \
      -command "set REPLY_3opts Exit ; destroy $w "

   pack  $w.fRbutts.buttMore \
      -side left \
      -anchor center \
      -fill none \
      -expand 1

   pack  $w.fRbutts.buttStop \
      -side left \
      -anchor center \
      -fill none \
      -expand 1

   pack  $w.fRbutts.buttExit \
      -side left \
      -anchor center \
      -fill none \
      -expand 1

   #################################################
   ## Set VARwidth & VARheight from $VARtext.
   ##
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################

   set VARlist [ split $VARtext "\n" ]

   ## FOR TESTING:
   #  puts "VARlist: $VARlist"

   set VARheight [ llength $VARlist ]

   ## FOR TESTING:
   #  puts "VARheight: $VARheight"

   $w.fRmsg.text configure -height $VARheight

   #################################################
   ## To get VARwidth,
   ##    loop through the 'lines' getting length
   ##     of each; save max.
   #################################################

   set VARwidth 0

   #############################################
   ## LOOK AT EACH LINE IN THE LIST.
   #############################################
   foreach line $VARlist {

      #############################################
      ## Get the length of the line.
      #############################################
      set LINEwidth [ string length $line ]


      if { $LINEwidth > $VARwidth } {
         set VARwidth $LINEwidth
      }

   }
   ## END OF  foreach line $VARlist

   set VARwidth [ expr $VARwidth + 1 ]

   $w.fRmsg.text configure -width  $VARwidth

   ## FOR TESTING:
   #   puts "VARwidth: $VARwidth"

   ########################################################################
   ## NOTE: VARwidth works for an adobe-courier fixed-width font used
   ##       for the text widget ... BUT the programmer may need to be
   ##       careful that the contents of VARtext
   ##       are all countable characters by the 'string length' command.
   ########################################################################

   ########################################################################
   ## Force a pause for input. (We expect a click on one of the 3 buttoms
   ## above, which would trigger a 'puts' and a 'destroy $w'.)
   ########################################################################
   raise $w

   tkwait window $w

}
## END OF 'popup_msg_3opts' PROCEDURE


##  'finalize_textwidget' PROCEDURE 
## PURPOSE: 1) Put tot-lines of text in label '.fRsearch.labLINES'.
##          2) Make the text 'read-only' -- i.e. non-editable.
## CALLED BY: 'readfile2textwidget' proc.  (below)

proc finalize_textwidget { } {

   ######################################################################
   ## Get total number of text widget lines, for [possible] display
   ## in the 'shofil' window, for user's edification.
   ######################################################################
   ## Gets the end of text widget in 'line.col' form by using 'index end'.
   ##   Reference: Page 156 of Eric Johnson book, "Graphical Applications
   ##              with Tcl and Tk".
   ## Then uses string commands to break this apart at the dot (.).
   ## We subract 2 from the 'line' figure to get the actual line count.
   ######################################################################
   set LASTline_dot_col  [ .fRmain.text index end ]

   ## FOR TESTING:
   # puts "LASTline_dot_col: $LASTline_dot_col"


   set DOTindex [ string first {.} $LASTline_dot_col ]
   set TOTlines [ string range $LASTline_dot_col 0 [ expr $DOTindex - 1 ] ]

   set TOTlines [ expr $TOTlines - 2 ]
   if { $TOTlines < 0 } { set TOTlines 1 }

   .fRsearch.labLINES configure  -text "Lines, of $TOTlines"

   ## FOR TESTING:
   # puts "TOTlines: $TOTlines"

   ########################################################################
   ## MAKE THE TEXT 'READ-ONLY' -- I.E. NON-EDITABLE.
   ########################################################################

       .fRmain.text configure -state disabled
   ##  .fRmain.text configure -state normal

   .fRmain.text configure -cursor xterm

}
## END OF 'finalize_textwidget' PROCEDURE


## 'readfile2textwidget' PROCEDURE
##
## PURPOSE: To load the contents of $FILEname into the text widget,
##          '.fRmain.text'.   Uses 'read' to read blocks, several 
##          thousand bytes at a time.
##
##          NOTE: There is NO logic to discover positions of
##                linefeed chars and then truncate long lines.
##
##          Handles huge files via a More/Stop/Exit dialog prompt.
##
## CALLED BY:  final GUI initialization section below
##

proc readfile2textwidget { } {

   global FILEname REPLY_3opts

   ########################################################################
   ## SETUP for the while-loop to READ input FILE INTO TEXT WIDGET.
   ########################################################################
   .fRmain.text delete 1.0 end

   set f [open $FILEname]

   set Bytes2read 1000
   set TotBytesRead 0
   set TotMegRead 0
   set TotBlocksRead 0
   set BlockCnt 0

   ## Prompt user whether to continue reading at Bytes2read*BlockCnt2Check.
   ## If Bytes2read=5000 & BlockCnt2Check=200, the product is 1 Meg.
   ## If Bytes2read=5000 & BlockCnt2Check=800, the product is 4 Meg.

   # set BlockCnt2Check 200
   # set BlockCnt2Check 400
     set BlockCnt2Check 800
   catch { set BlockCnt2Check "$env(BLOCKCNT2CHECK)" }

   set REPLY_3opts ""

   ## FOR TESTING:
   #  set BlockCnt2Check 2

   while {![eof $f]} {

      ## In case text widget is 'disabled', unset it to allow update.
      .fRmain.text configure -state normal

      ## NOTE: The 'read' number here should be the same as set 
      ##       in the Bytes2read var above.
      .fRmain.text insert end [read $f 1000]

      ## Undo the '-state normal' above.
      ##    This 'disabled' statement is also called
      ##    in  proc 'finalize_textwidget', which is called
      ##    at the end of loading the text widget,
      ##    as well as one or more times while loading a huge file.
      ##    We want to make sure the text area is disabled at
      ##    essentially all times during loading that area.
      .fRmain.text configure -state disabled

      set TotBytesRead  [expr $TotBytesRead + $Bytes2read]
      set TotBlocksRead [expr $TotBlocksRead + 1]
      set BlockCnt [expr $BlockCnt + 1]
      set TotMegRead  [expr $TotBytesRead / 1000000.0]
      # set TotMegRead  [format "%f" [expr $TotBytesRead / 1000000.0]]

      ## FOR TESTING:
      #   puts "*************************"
      #   puts "TotBytesRead   : $TotBytesRead"
      #   puts "TotBlocksRead  : $TotBlocksRead"
      #   puts "BlockCnt       : $BlockCnt"
      #   puts "TotMegRead     : $TotMegRead"
      #   puts "BlockCnt2Check : $BlockCnt2Check"

      if { $BlockCnt > $BlockCnt2Check } {

         ## Double 'BlockCnt2Check' at each read iteration.
         set BlockCnt2Check [expr $BlockCnt2Check + $BlockCnt2Check]

         finalize_textwidget

         popup_msg_3opts "\
$TotMegRead Megabytes have been read from file
$FILEname

Do you want to read More, Stop, or Exit (Quit,Cancel)?" 

         set BlockCnt 0

         ## FOR TESTING:
         #   puts "*************************"
         #   puts "popup_msg_3opts reply: $REPLY_3opts"

         if {"$REPLY_3opts" == "More"} {
            continue
         }

         if {"$REPLY_3opts" == "Stop"} {
            finalize_textwidget
            close $f
            return
         }

         if {"$REPLY_3opts" == "Exit"} {
            close $f
            exit
         }

      }
      ## END OF  if { $BlockCnt > $BlockCnt2Check }
   }
   ## END OF   while {![eof $f]}

   finalize_textwidget

   close $f

   if {"$REPLY_3opts" == "More"} {
      popup_msg "Finished reading file. Total Megabytes: $TotMegRead"
   }

}
## END of proc 'readfile2textwidget'


## 'getfile2textwidget' PROCEDURE
##
## PURPOSE: To load the contents of $FILEname into the text widget,
##          '.fRmain.text'.  Uses 'gets' to read line-by-line, i.e.
##          to each newline character. Truncates long lines.
##
##          Handles huge files via a More/Stop/Exit dialog prompt.
##
## CALLED BY:  final GUI initialization section below
##
##             (an alternative to proc 'readfile2textwidget')

proc getfile2textwidget { } {

   global FILEname REPLY_3opts

   ########################################################################
   ## Set up for READing FILE INTO TEXT WIDGET.
   ########################################################################
   .fRmain.text delete 1.0 end

   set f [open $FILEname]

      ## FOR TESTING:
      #   puts "*************************"
      #   puts "Starting proc getfile2textwidget."
      #   puts "Opened file $FILEname"

   set TotBytesRead 0

   set line ""

   set maxLen 1000

   ## Set a size parameter used to determine when to
   ## prompt user whether to continue reading, at about 4 Meg.
   set TotBytes2Check 4000000
   catch { set TotBytes2Check "$env(TOTBYTES2CHECK)" }

   ## FOR TESTING More/Stop/Exit prompt:
   #  set TotBytes2Check 4000

   ## Var to hold 'More', 'Stop', or 'Exit'.
   set REPLY_3opts ""

   ## START OF WHILE-LOOP for the 'gets' file-READING.
   ## The while-test below is equivalent to 'while {![eof $f]}'.
   while {[eof $f] == 0} {

      ## In case text widget is 'disabled', unset it to allow update.
      .fRmain.text configure -state normal

      ## GET the next line (up to a line feed) --- and its length.
      set lineLen [gets $f line]

      ## If line too long, truncate.
      if { $lineLen > $maxLen } {
         set line "[string range $line 0 $maxLen]...xpg:truncated-this-line-in-this-window"
         # set lineLen $maxLen
      }

      ## ADD the line (truncated if long) to the text widget, with a linefeed.
      .fRmain.text insert end "$line\n"

      ## Undo '-state normal', set above.
      ##   This 'disabled' statement
      ##   is also called in  proc 'finalize_textwidget',
      ##   which is called at the end of loading the text
      ##   widget, as well as one or more times while loading
      ##   a huge file.
      .fRmain.text configure -state disabled

      set TotBytesRead [expr $TotBytesRead + $lineLen]

      ## FOR TESTING:
      #   puts "*************************"
      #   puts "TotBytesRead   : $TotBytesRead"
      #   puts "TotBytes2Check : $TotBytes2Check"
      #   puts "Cur. lineLen   : $lineLen"
      #   puts "line           : $line"


      if { $TotBytesRead > $TotBytes2Check } {

         ## Double 'TotBytes2Check' at each prompt.
         set TotBytes2Check [expr $TotBytes2Check + $TotBytes2Check]

         finalize_textwidget

         set TotMegRead  [expr $TotBytesRead / 1000000.0]
         popup_msg_3opts "\
$TotMegRead Megabytes have been read from file
$FILEname

Do you want to read More, Stop, or Exit (Quit,Cancel)?" 

         ## FOR TESTING:
         #   puts "*************************"
         #   puts "popup_msg_3opts reply: $REPLY_3opts"

         if {"$REPLY_3opts" == "More"} {
            continue
         }

         if {"$REPLY_3opts" == "Stop"} {
            finalize_textwidget
            close $f
            return
         }

         if {"$REPLY_3opts" == "Exit"} {
            close $f
            exit
         }

      }
      ## END OF  if { $TotBytesRead > $TotBytes2Check }
   }
   ## END OF   while {![eof $f]}

   finalize_textwidget

   close $f

   if {"$REPLY_3opts" == "More"} {
      set TotMegRead  [expr $TotBytesRead / 1000000.0]
      popup_msg "Finished reading file. Total Megabytes: $TotMegRead"
   }

}
## END of proc 'getfile2textwidget'



## Set window colors according to r255,g255,b255, which were initialized
## near the top of this script where various widget colors were set.

set_text_colors


## Force the text window to popup before a '.topmsg' window that
## prompts the user with More,Stop,Exit buttons --- if the input
## file is huge. The '.topmsg' window comes from execution of
## the following call to a proc to load the text widget.

update

## Read $FILEname into '.fRmain.text'.
## 'getfile2textwidget' is safer to use because it uses 'gets' to read
##  one line (i.e. up to a newline character) at a time, and if the line
##  is very long (more than about 1k bytes or whatever is set in the
##  var maxLen), the line is truncated. This can avoid problems with
##  a crash when using the horizontal scroll bar.
##  'readfile2textwidget' WAS used, but it can cause crashes --- if
##  the user horizontally-scrolls the window at long lines.
##  It uses 'read' to read blocks of several thousand bytes at a time,
##  with no (complex) code to check for distance between newline chars
##  and hence no truncation of long lines.
##  I did not have troubles with crashes when using 'readfile2textwidget'
##  on Ubuntu 9.10, but on Ubuntu 10.10 some lines more than about
##  1,075 bytes long along with use of the horizontal scroll bar
##  caused crashes --- with briefly seen 'AppArmor' and 'format' messages
##  as the X-server crashed and a login prompt re-appeared. I could
##  not find the complete messages in log files, like 'Xorg.0.log.old'.
## In the unlikely case where use of the 'getfile' proc causes a problem,
## the user can comment out 'getfile2textwidget' and de-comment
## 'readfile2textwidget' --- to give the 'readfile' proc a try on files with
## long lines, including big binary files, like large compiled executables.
## OLD, RETIRED:
## readfile2textwidget  

getfile2textwidget

Here is a screenshot that shows the frame revealed by clicking on the 'OtherOpts' button.

For this image, I used the 'WinColor' button to bring up the color-selector GUI and set the window color to a brown that sort of matches the brown shades that were common to the Ubuntu Linux desktop in the 2008-2009 era.

Here is code for the 4 shell scripts of the 'xpg' system. (Perhaps these code segments should be posted on a separate page, since this page is quite long already.)

 Code for the 'findANDshow_stringsINfile_plusminusNlines.sh' script:

#!/bin/sh
##
## SCRIPT: findANDshow_stringsINfile_plusminusNlines.sh
##
## adapted from the 'FE xpg' subsystem of the Freedom Environment system,
## whose home page is at www.freedomenv.com.
##
## This is a 'stand-alone' version for contribution to wiki.tcl.tk
##
## PURPOSE: Let $1 $2 $3 $4 represent the 4 positional arguments to this script
##          --- <string> <integer> <yes/no> <filename>.
##
##          For the filename specified in $4, this script finds the lines in
##          the file which contains a match to the string in $1 ---
##          and it shows N lines above and below the 'match' lines, where
##          N=$2. It uses case-sensitivity in the search, depending on
##          whether $3 is 'yes' or 'no'.
##
##          Like 'egrep', the string argument may be sub-strings separated
##          by vertical-bars (|).  Hence, the file can actually be searched
##          for an OR-match, in any record of the file, to the several
##          sub-strings within the string.
##
##          Example string:  'fatal|error|fail|warning'
##
##          NOTE: INCLUDING THE SINGLE QUOTES. They are needed to keep the
##                shell from trying to execute the string, esp. when it
##                contains special characters like < or > or [ or ] or
##                parentheses or ! or whatever.
##
##         'awk' is used to find the match(es) and save-and-print the N lines
##         above a match line as well as the N lines following, where N = $2.
##
##         This is an 'egrep-like' utility --- except that it is an extended
##         'egrep' (one might say an 'eegrep') utility that shows lines
##         around the match lines.  This script does this with 'awk',
##         because 'egrep' is not capable of showing the nearby lines.
## CALL FORMAT:
##
## $DIRxpg/findANDshow_stringsINfile_plusminusNlines.sh \
##                             <string> <integer> <yes/no> <filename>
##
## CALLED BY: the tk-GUI utility script  'shofil.tk'
##
##            to support the unique 'Show All Matches' productivity feature.
##
## MAINTENANCE HISTORY for this 'stand-alone' version:
##
## Written: Blaise Montandon 2013aug05  Started work on creating this
##                                      'stand-alone' version from the
##                                      'integrated' 'FE xpg' subsystem at
##                                      www.freedomenv.com.
## Updated: Blaise Montandon 20.......                            

THISscript="$0"
THISscriptBASE=`basename "$THISscript"`

## Save input, for err msg below.

  ALLinput="$*"

## FOR TESTING:
#    echo "
#    ALLinput: $ALLinput"

## Get input items #1 and #2 and #3 and #4 from one string of input.
## Based on the following '$1' and 'shift' example,
## from /apps/ideas_9/bin/msplot,
## which preserves any arguments in quotations:
##
## while test "$1" != ""
##    APP_ARGS="$APP_ARGS \"$1\""
##    shift
## done

## 1) Get the first of 4 parms --- the <string> parm.
## Use 'eval' to remove single-quotes that protect the string from
## interpretation by the shell.  Now the string can be special
## characters like '>'. 
# eval STRINGin=$1
## seems to work OK. But let's try double-quotes around $1.    
  eval STRINGin="$1"

## Change  "\" to "\\" --- to avoid awk err msg
## 'Newline in string  ... at source line 1'
if test "$STRINGin" = '\' 
then
    STRINGin='\\'
fi

## FOR TESTING:
#    echo "
#    STRINGin: $STRINGin"

shift
Nlines="$1"

## FOR TESTING:
#    echo "
#    Nlines: $Nlines"

shift
CaseSense="$1"

## FOR TESTING:
#    echo "
#    CaseSense: $CaseSense"


shift
FILEin="$1"

## FOR TESTING:
#    echo "
#    FILEin: $FILEin"

## Check for input item #1 and #2 and #3 and #4.

ERRMSG_MAIN="\
***********
INPUT ERROR:
Supply 4 inputs --- a STRING and an INTEGER (Nlines) and
                    yes/no (CaseSense) and a FILENAME
to script
$THISscript.

INPUT FORMAT: <STRING> <INTEGER> <yes/no> <FILENAME>

EXAMPLES: 
  $THISscriptBASE  'error'  3 no /var/adm/SYSLOG
  $THISscriptBASE  'error|warning|core|dump' 3 no /var/adm/SYSLOG
  $THISscriptBASE  'file systems' 3 yes /var/adm/SYSLOG

CURRENT INPUT: $ALLinput
"

if test "$STRINGin" = "" 
then
   echo "
$ERRMSG_MAIN

Supply a (search) STRING to script
$THISscript.

Exiting ...
"
   exit
fi

if test "$Nlines" = ""
then
   echo "
$ERRMSG_MAIN

Supply an INTEGER (plus-minus-Num-Lines) to script
$THISscript.

Exiting ...
"
   exit
fi

if test "$CaseSense" = ""
then
   echo "
$ERRMSG_MAIN

Supply a Case-Sensitivity Indicator (yes/no) to script
$THISscript.

Exiting ...
"
   exit
fi

if test "$FILEin" = "" 
then
   echo "
$ERRMSG_MAIN

Supply the FILENAME (of the file to search) to script
$THISscript.

Exiting ...
"
   exit
fi


## Set DIRxpg for use in the 'stand-alone' version of the 'xpg' system.

# DIRxpg="$HOME/apps/xpg"
DIRxpg="."


## PREPARE A REPORT FILE --- and its HEADING.
## (Put the lines in a local file whose name is built in $OUTLIST,
##  by the 'set_localoutlist' utility.)

## This dot (.) is not needed. It just seems to be a little more efficient
## to not spawn off a new shell environment to run this script, if we
## do not need to do so.
. $DIRxpg/set_localoutlist.sh


CaseSenseMsg="( CASE-SENSITIVE! )"
if test "$CaseSense" = "no"
then
   CaseSenseMsg="(upper or lower case ; that is, CASE-INSENSITIVE)"
fi

MULTICHECK=`echo "$STRINGin" | grep '|'`
if test "$MULTICHECK" = ""
then
   StringMsg="STRING"
else
   StringMsg="STRINGS separated by '|' in "
fi

   echo "\
********************* `date '+%Y %b %d  %a  %T%p %Z'` ******************

'MATCH' LINES FROM FILE

        $FILEin

Lines that contain the $StringMsg '$STRINGin'

                    $CaseSenseMsg

--- INCLUDING $Nlines line(s) ABOVE-AND-BELOW 'match' lines.


                 All lines are preceded by line numbers.

                 An asterisk (*) before a line-number indicates a match,
                 to the string, was found in the line.

LineNumber:Text
----------------------------------------------------------------------------
" >  "$OUTLIST"



## CALL 'awk' -- with an appropriate awk program -- with a file as input. 
## 'awk' program (AN EXAMPLE) to
##  write all lines whose first field is different from the previous one.
##
##               $1 != prev { print; prev = $1 }
##  NOTE:
##  This extended-egrep 'eegrep' script is basically
##  a more complex version of this example.

## FOR TESTING:
#  TEST="YES"
   TEST="NO"

if test "$TEST" = "YES"
then

echo "
*..........................................................
* Lines in file $FILEin
* that match the '|'-separated sub-strings in '$STRINGin'
* --- including $Nlines line(s) above-and-below matches
* --- are shown below.
*..........................................................
*  All lines are preceded by line numbers.
*  An asterisk (*) before a line-number indicates a match.
*..........................................................
"
  # set -x

fi


## HERE's the 'awk'.
## Add 'cut -c1-3071 $FILEin |' before
## awk, to avoid 'Input record too long'
## error that stops awk dead.

cut -c1-3071 "$FILEin" | \
awk  -v N="$Nlines"  -v STRING="$STRINGin" -v CASESENSE="$CaseSense" \
'BEGIN  {

   #######################################################
   ## Initialize the N "prev" vars to null.
   ## They are to hold the last N lines read.
   #######################################################
   for ( i = 1 ; i <= N ; i++ )
   {
       prev[i] = ""
   }

   ##################################################
   ## After converting STRING to upper-case,
   ## if CASESENSE=no,
   ## split the "STRING" into NS "subSTRING"s -- at
   ## occurrences of a vertical bar (|).
   ##################################################

   if ( CASESENSE == "no" ) {
      STRING = toupper(STRING)
   }

   NS=split(STRING,subSTRING,"|")


   ## FOR TESTING:
   #    print "CASESENSE: " CASESENSE
   #    print "NS: " NS
   #    print "subSTRING[1] :" subSTRING[1]
   #    print "subSTRING[2] :" subSTRING[2]
   #    print "subSTRING[3] :" subSTRING[3]

   ###################################################
   ## "aftcount" holds the integer N,N-1,...,2,1, or 0
   ## --- representing the number of lines after the
   ## last matched line that still need to be printed.
   ###################################################
   aftcount = 0

   ######################################################
   ## "lastprt" holds the line# of the line last printed.
   ## "lastprt" is reset any time "printf" is called.
   ######################################################
   lastprt = 0

}
#END OF BEGIN
#START OF BODY
{
   ####################################################
   ## IF WE HAVE A MATCH, SUSPEND PRINTING
   ## at N "AFTER-A-MATCH-LINES":
   ## If there is a new match, reset "aftcount" to zero.
   ##            (We do not want to print a line twice.)
   ## We will restart aftcount at N after the new match
   ## line is printed.
   ####################################################
   ## We use "Match" to indicate whether there was a
   ## match to at least one of the subSTRINGs, in the
   ## current line ($0).  Match==1 indicates a match.
   ####################################################

   Match = 0

   if ( CASESENSE == "no" ) {
      HOLDline = toupper($0)
   } 
   if ( CASESENSE == "yes" )  {
      HOLDline = $0
   }

   ## FOR TESTING:
   #   if  ( NR < 10 ) { print "HOLDline :" $HOLDline }

   for  ( i = 1 ; i <= NS ; i++ ) {  

       ## This fails when certain special chars are in the substring.
       # if (  HOLDline ~ subSTRING[i] )  { aftcount = 0 ; Match = 1 }

        if ( index(HOLDline,subSTRING[i]) != 0  )  { aftcount = 0 ; Match = 1 }

       ## FOR TESTING:
       #   print ""
       #   print "HOLDline: " HOLDline
       #   print "subSTRING LOOP - i: " i " subSTRING[i]: " subSTRING[i] " aftcount: " aftcount " Match: " Match
       #   print "index(HOLDline,subSTRING[i]): "index(HOLDline,subSTRING[i])

   }

   ## FOR TESTING:
   # }" "$FILEin"
   # exit

   ######################################################
   ## PRINT ONE OF THE N "AFTER-A-MATCH-LINES":
   ## If "aftcount" is non-zero, print the current line.
   ## We had a match up to N lines ago. Decrement "aftcount"
   ## and save the number of the printed line in "lastprt".
   ######################################################

   if ( aftcount != 0 ) {

       printf (" %s : %s \n", NR, $0);

       ## If this is the last of the "aftcount" lines,
       ## print a blank line.
       if ( aftcount == 1 ) {print ""}

       aftcount = aftcount - 1 ;
       lastprt = NR

       ## FOR TESTING:
       #  print "aftcount != 0  CHECK::  aftcount: " aftcount " lastprt: " lastprt
   }

   ## FOR TESTING:
   # }" "$FILEin"
   # exit


   ######################################################
   ## IF WE HAVE A MATCH, PRINT N-PREV & CURRENT:
   ## If there is a match, print the N previous lines
   ## --- as long as their linenums are greater than
   ## the last-printed line number.  (We do not want
   ## to print a line twice.)
   ##
   ## Then print the current line.  Also set "aftcount"
   ## to N, and save the
   ## number of the matched-printed line in "lastprt".
   ######################################################

   for  ( i = N ; i > 0 ; i-- ) {  

       recnum = NR - i
       if ( Match == 1 && recnum > lastprt ) {
             printf (" %s : %s \n", recnum, prev[i])
       }

       ## FOR TESTING:
       #  print "prev[] PRINT-LOOP::  NR= " NR " recnum= " recnum " i= " i
       #  print "prev[] PRINT-LOOP::  lastprt= " lastprt " prev[i]= " prev[i]
   }

   if ( Match == 1 ) {

       printf ("*%s : %s \n", NR, $0);
       aftcount = N;
       lastprt = NR

       ## FOR TESTING:
       #  print "Match == 1 TEST::  aftcount: " aftcount " lastprt: " lastprt
   }


   ########################################################
   ## Update prev[N], prev[N-1], ... , prev[2], and prev[1]
   ## before reading the next line.
   ########################################################

   for  ( i = N ; i > 1 ; i-- )
   {  
     prev[i] = prev[i-1]
   }

   prev[1] = $0

#END OF BODY
}' >> "$OUTLIST"
## WAS:
## }' "$FILEin" >> "$OUTLIST"


## ADD A TRAILER TO THE REPORT-FILE.

      echo "
----------------------------------------------------------------------------

The report above was created by the utility script

    $THISscriptBASE

The script uses an 'awk' program that essentially extends the
capabilities of the 'egrep' (extended grep) program. ['grep' is a program
that can find lines in a file that contain a given string of characters.]

'egrep' can show the lines of a file that contain matches to *one-or-more*
strings.  Example: 'error', 'fail', 'fatal', or 'warning'.

With 'egrep', the multiple-strings argument is formed by separating the
multiple strings by vertical-bars (|).  Example:  'fatal|error|fail|warning'

But 'egrep' cannot show nearby lines.  The 'awk' program used here essentially
creates an extension of the 'egrep' (extended grep) utility.

As the name (above) of the utility script implies, this utility can show
plus-or-minus N lines above and below the lines that have a match for
the search string(s).

You could say this is an 'eegrep' utility --- extended, extended grep.

----------------------------------------------------------------------------

With 'egrep', one can make the search case-insenstive with the '-i' option.
Likewise, this '$THISscriptBASE' utility
can be told to make the search either case-insensitive or case-sensitive.

As indicated above, like 'egrep', this '$THISscriptBASE'
utility accepts the  '|' character.  Then, to find all lines containing
either 'memory' or 'RAM' or 'disk', you can use the search string 
'memory|ram|disk' --- with case-sensitivity switch OFF.

If that returns too many lines with the string 'ram' --- lines with words
like 'datagram' or 'telegram' or 'ramble' or 'gram' --- then switch the
case-sensitivity switch to ON --- and use a string like 'memory|RAM|disk'.

----------------------------------------------------------------------------

The script
 
    $THISscriptBASE

is called from within the FE system 'xpg' text-file
browse/extract/print utility.

  [Actually, the 'xpg' script calls the Tcl-Tk GUI script
   $DIRxpg/shofil.tk
   --- and it is this Tcl-Tk script that calls the script
   $THISscriptBASE.]

---------------------------------------------------------------------------

The 'Extract-String-Match-Lines' capability of the 'xpg' utility is
very useful --- especially to people who deal with huge files such as
system log files and huge lists (say of files) and large README-like help files
--- people like system administrators and application developers and engineers.

---------------------------------------------------------------------------

If 'xpg' is 'not found' when you type 'xpg' at a shell command prompt,
you can make an alias for 'xpg' by putting the following alias definition
in your shell 'rc' (run control) file --- such as $HOME/.bashrc or
$HOME/.bash_aliases.

        alias xpg='$HOME/apps/bin/xpg'

Then logoff-logon to make the alias available in every window of a
login session.

---

You could also make a desktop icon for the
$HOME/apps/bin/xpg utility,
and drop text files on it.

---

If you type 'xpg', without an input filename, at a command prompt, you
will see a usage hint like the following.

Usage:  xpg  [-h]  [-f]  file1 [ file2 ... file8 ]

I.e. xpg is setup to browse up to 8 files at a time. The limit is to help
avoid accidentally opening up more xpg-windows than one wants to deal with.
This can happen if you have an 'xpg' icon on your desktop and you
unintentionally drag a sheet-load of filenames onto the icon.

********************* `date '+%Y %b %d  %a  %T%p %Z'` ******************
" >>  "$OUTLIST"


## SHOW THE REPORT-FILE OF ERR LINES FROM THE SELECTED PRINT FILE, $FILEIN.

$DIRxpg/xpg  "$OUTLIST"

 Code for the 'findANDshow_NOTstringsINfile.sh' script:

#!/bin/sh
##
## SCRIPT: findANDshow_NOTstringsINfile.sh
##
## adapted from the 'FE xpg' subsystem of the Freedom Environment system,
## whose home page is at www.freedomenv.com.
##
## This is a 'stand-alone' version for contribution to wiki.tcl.tk
##
## PURPOSE: Let $1 $2 $3 represent the 3 positional arguments to this script --
##           <string> <yes/no> <filename>.
##
##          For the filename specified in $3, this script finds the lines in
##          the file which DO NOT contain a match to the string(s) in $1.
##          It uses a case-sensitive search, depending on whether $2 is
##          'yes' or 'no'.
##
##          Like 'egrep', the string argument may be sub-strings separated
##          by vertical-bars (|).  Hence, the file can actually be searched
##          for a NOT-AND-match, in any record of the file, to the several
##          sub-strings within the string.
##
##          Example string:  'fatal|error|fail|warning'
##
##          NOTE: INCLUDING THE SINGLE QUOTES. They are needed to keep the
##                shell from trying to execute the string, esp. when it
##                contains special characters like < or > or [ or ] or
##                parentheses or ! or whatever.
##
##         'awk' is used to find the NON-match(es) lines of the input file.
##
##         This is an 'egrep-like' utility. It is like 'egrep -v' or
##         'egrep -i -v'.
##
##         However, by using 'awk', we might extend this search someday to
##         accept an Nlines integer input --- and then show plus-or-minus N lines
##         around the NON-match lines.  This script would do this with 'awk',
##         because 'egrep' is not capable of showing the nearby lines.
## CALL FORMAT:
##
## $DIRxpg/findANDshow_NOTstringsINfile.sh <string> <yes/no> <filename>
##
## CALLED BY: the tk-GUI utility script  'shofil.tk'
##
##            to support the unique 'Show All Matches' productivity feature.
##
## MAINTENANCE HISTORY for this 'stand-alone' version:
##
## Written: Blaise Montandon 2013aug05 Started work on creating this
##                                      'stand-alone' version from the
##                                      'integrated' 'FE xpg' subsystem at
##                                      www.freedomenv.com. 
## Updated: Blaise Montandon 20.......                                       

THISscript="$0"
THISscriptBASE=`basename "$THISscript"`

## Save input, for err msg below.

  ALLinput="$*"

## FOR TESTING:
#    echo "
#    ALLinput: $ALLinput"

## Get input items #1 and #2 and #3 from one string of input.
## Based on the following '$1' and 'shift' example,
## which preserves any arguments in quotations:
##
## while test "$1" != ""
##    APP_ARGS="$APP_ARGS \"$1\""
##    shift
## done

## 1) Get the first of 3 parms --- the <string> parm.
## Use 'eval' to remove single-quotes that protect the string from
## interpretation by the shell.  Now the string can be special
## characters like '>'. 
# eval STRINGin=$1
## seems to work OK. But let's try double-quotes around $1.    
  eval STRINGin="$1"

## Change  "\" to "\\" --- to avoid awk err msg
## 'Newline in string  ... at source line 1'
if test "$STRINGin" = '\' 
then
    STRINGin='\\'
fi

## FOR TESTING:
#    echo "
#    STRINGin: $STRINGin"

shift
CaseSense="$1"

## FOR TESTING:
#    echo "
#    CaseSense: $CaseSense"


shift
FILEin="$1"

## FOR TESTING:
#    echo "
#    FILEin: $FILEin"

## Check for input item #1 and #2 and #3.

ERRMSG_MAIN="\
***********
INPUT ERROR:
Supply 3 inputs --- a STRING and yes/no (CaseSense) and a FILENAME
to script
$THISscript.

INPUT FORMAT: <STRING> <yes/no> <FILENAME>

EXAMPLES: 
  $THISscriptBASE  'error' no /var/adm/SYSLOG
  $THISscriptBASE  'error|warning|core|dump' no /var/adm/SYSLOG
  $THISscriptBASE  'file systems' yes /var/adm/SYSLOG

CURRENT INPUT: $ALLinput
"

if test "$STRINGin" = "" 
then
   echo "
$ERRMSG_MAIN

Supply a (search) STRING to script
$THISscript.

Exiting ...
"
   exit
fi

if test "$CaseSense" = ""
then
   echo "
$ERRMSG_MAIN

Supply a Case-Sensitivity Indicator (yes/no) to script
$THISscript.

Exiting ...
"
   exit
fi

if test "$FILEin" = "" 
then
   echo "
$ERRMSG_MAIN

Supply the FILENAME (of the file to search) to script
$THISscript.

Exiting ...
"
   exit
fi


## Set DIRxpg for use in the 'stand-alone' version of the 'xpg' system.

# DIRxpg="$HOME/apps/xpg"
DIRxpg="."


## PREPARE A REPORT FILE --- and its HEADING.
## (Put the lines in a local file whose name is built in $OUTLIST,
##  by the 'set_localoutlist' utility.)

## This dot (.) is not needed. It just seem to be a little more efficient
## to not spawn off a new shell environment to run this script, if we
## do not need to do so.
. $DIRxpg/set_localoutlist.sh


CaseSenseMsg="(CASE-SENSITIVE)"
if test "$CaseSense" = "no"
then
   CaseSenseMsg="(upper or lower case ; that is, CASE-INSENSITIVE)"
fi


MULTICHECK=`echo "$STRINGin" | grep '|'`
if test "$MULTICHECK" = ""
then
   StringMsg="string"
else
   StringMsg="any of the strings separated by '|' in "
fi


   echo "\
********************* `date '+%Y %b %d  %a  %T%p %Z'` ******************

'NON-MATCH' LINES FROM FILE

        $FILEin

Lines that DO NOT contain $StringMsg  '$STRINGin'

$CaseSenseMsg

                 All lines are preceded by line numbers.

LineNumber:Text
----------------------------------------------------------------------------
" >  "$OUTLIST"



## CALL 'awk' -- with an appropriate awk program -- with a file as input. 
## 'awk' program (AN EXAMPLE) to
##  write all lines whose first field is different from the previous one.
##
##               $1 != prev { print; prev = $1 }
##  NOTE:
##  This extended-egrep 'eegrep' script is basically
##  a more complex version of this example.

## FOR TESTING:
#  TEST="YES"
   TEST="NO"

if test "$TEST" = "YES"
then

echo "
*..........................................................
* Lines in file $FILEin
* that DO NOT match ANY of the '|'-separated sub-strings in
* the string
*            '$STRINGin'
* are shown below.
*..........................................................
"
  # set -x

fi


## HERE's the 'awk'.
## Add 'cut -c1-3071 $FILEin |' before
## awk, to avoid 'Input record too long'
## error that stops awk dead (on SGI-IRIX).

cut -c1-3071 "$FILEin" | \
awk -v STRING="$STRINGin" -v CASESENSE="$CaseSense" \
'BEGIN  {

   ##################################################
   ## After converting STRING to upper-case,
   ## if CASESENSE=no,
   ## split the "STRING" into NS "subSTRING"s -- at
   ## occurrences of a vertical bar (|).
   ##################################################

   if ( CASESENSE == "no" ) {
      STRING = toupper(STRING)
   }

   NS=split(STRING,subSTRING,"|")


   ## FOR TESTING:
   #    print "CASESENSE: " CASESENSE
   #    print "NS: " NS
   #    print "subSTRING[1] :" subSTRING[1]
   #    print "subSTRING[2] :" subSTRING[2]
   #    print "subSTRING[3] :" subSTRING[3]

   ######################################################
   ## "lastprt" holds the line# of the line last printed.
   ## "lastprt" is reset any time "printf" is called.
   ######################################################
   lastprt = 0

}
#END OF BEGIN
#START OF BODY
{

   if ( CASESENSE == "no" ) {
      HOLDline = toupper($0)
   } 
   else {
      HOLDline = $0
   }

   ## FOR TESTING:
   #   if  ( NR < 10 ) { print "HOLDline :" $HOLDline }

   ####################################################
   ## We use "Match" to indicate whether there was a
   ## match to the subSTRINGs, in the current
   ## line ($0).  Match==1 indicates a match to at least
   ## ONE the substrings, which means we do NOT print
   ## the line.  We print the line if Match==0.
   ####################################################

   Match = 0

   for  ( i = 1 ; i <= NS ; i++ ) {  

         if ( index(HOLDline,subSTRING[i]) != 0  )  {  Match = 1 }

       ## FOR TESTING:
       #  print "subSTRING LOOP - subSTRING[i]: " subSTRING[i] "  Match: " Match
       #
       #  print "index(HOLDline,subSTRING[i]): "index(HOLDline,subSTRING[i])

   }

   ## FOR TESTING:
   # }" "$FILEin"
   # exit

   ######################################################
   ## PRINT THE LINE if Match == 0 (none of the substrings
   ## had a match in the line).
   ######################################################

   if ( Match == 0 ) {

       printf (" %s : %s \n", NR, $0);

       lastprt = NR

       ## FOR TESTING:
       #  print "Match == 0  --- lastprt: " lastprt
   }

   ## FOR TESTING:
   # }" "$FILEin"
   # exit


#END OF BODY
}' >> "$OUTLIST"
## }' $FILEin >> "$OUTLIST"


## ADD A TRAILER TO THE REPORT-FILE.

      echo "
----------------------------------------------------------------------------

The report above was created by the utility script

    $THISscriptBASE

The script uses an 'awk' program that shows all lines of an input file
EXCEPT the lines which contain a match to at least one of *one-or-more*
strings.  Example: 'error', 'fail', 'fatal', or 'warning'.

A multiple-strings argument is formed by separating the
multiple strings by vertical-bars (|).  Example:  'fatal|error|fail|warning'

----------------------------------------------------------------------------

As an example of how this 'NOT' search capability works, if you use the
string '##' and search one of the scripts in any of the FE subsystems,
you will see the script with most of the comment lines removed.

----------------------------------------------------------------------------

You can eliminate more comment lines by using the search string '##|# '
where there is a space character after the #-sign in the 2nd string.

      (The space character is helpful to avoid eliminating lines with
       a string like '#c0c0ff' which could be a hex string specifying
       a color for a GUI.)

----------------------------------------------------------------------------

And you can use the 'NOT' search capability following a search without 'NOT'.
For example, if you want to see all the lines of a script that contain the
string 'HOME', you could do a search without 'NOT'. Then, if that returned
so many comment lines that the extraction-list was hard to read, apply a
'NOT' search, on that extraction list which is being shown in an 'xpg' window,
using the string '##' . In the resulting 3rd 'xpg' window, you will find
an extraction-list with mostly non-comment lines that contain the string 'HOME'.

Alternatively, you can do the 'NOT' search FIRST, to get rid of most of
the comment lines. And THEN do an 'xpg' search with string 'HOME', with the
'NOT' checkbutton turned OFF.

----------------------------------------------------------------------------

The script  $THISscriptBASE

is called from within the FE system 'xpg' text-file
browse/extract/print utility.

  [Actually, the 'xpg' script calls the Tcl-Tk GUI script
   $DIRxpg/shofil.tk
   --- and it is this Tcl-Tk script that calls the script 
   $THISscriptBASE.]

---------------------------------------------------------------------------

If 'xpg' is 'not found' when you type 'xpg' at a shell command prompt,
you can make an alias for 'xpg' by putting the following alias definition
in your shell rc (run control) file --- such as $HOME/.bashrc or
$HOME/.bash_aliases.

        alias xpg='$HOME/apps/bin/xpg'

Then logoff-logon to make the alias available in every window of a
login session.

---

You could also make a desktop icon for the
$HOME/apps/bin/xpg utility,
and drop text files on it.

---

If you type 'xpg', without an input filename, at a command prompt, you
will see a usage hint like the following.

Usage:  xpg  [-h]  [-f]  file1 [ file2 ... file8 ]

I.e. xpg is setup to browse up to 8 files at a time. The limit is to help
avoid accidentally opening up more xpg-windows than one wants to deal with.
This can happen if you have an 'xpg' icon on your desktop and you
unintentionally drag a sheet-load of filenames onto the icon.

********************* `date '+%Y %b %d  %a  %T%p %Z'` ******************
" >>  "$OUTLIST"


## SHOW THE REPORT-FILE OF ERR LINES FROM THE SELECTED PRINT FILE, $FILEIN.

$DIRxpg/xpg  "$OUTLIST"

 Code for the 'set_localoutlist.sh' script:

#!/bin/sh
##
## SCRIPT NAME: set_localoutlist.sh
##
## Where:  in $FE_SCRIPTS_DIR=$FEDIR/scripts
##
##         where $FEDIR represents the install directory of an FE subsystem.
##
## PURPOSE:
##    Sets OUTLIST="/tmp/$USER/temp.lis####", if the /tmp/$USER directory
##    exists.
##    Otherwise, it sets OUTLIST="$HOME/tmp/temp.lis####". 
##
##        Here #### represents 4 integers generated near-randomly from the
##        minutes & seconds in the time of day.
##
##   In addition, to avoid accumulation to 'temp.lis' files, there are
##   two sections at the bottom of the script to remove
##
##   a) all but the N most-recent 'temp.lis####...' files, where N is
##      an integer like 50 or 100.
##   b) the big 'temp.lis####...' files, where 'big' is say > 50Meg.
## TYPICAL CALL FORMAT:  . $FEDIR/scripts/set_localoutlist.sh
## 
## WHERE USED:
##     'set_localoutlist.sh' is used by FE subsystem scripts, like the
##                'findANDshow_stringINfile_plusminusNlines.sh'
##     script of the FE 'xpg' subsystem.
##
##  You can use the command
##                     grep 'set_localoutlist' *
##  in a $FEDIR/scripts directory to show scripts that use
##  'set_localoutlist.sh'.
##
## MAINTENANCE HISTORY:
## Updated by: Blaise Montandon 2008mar24 Started the 'set_localoutlist.sh'
##                                        script on Linux, using Mandriva2007.
## Updated by: Blaise Montandon 2009nov11 Restarted development, on Ubuntu.
##                                        Found that 'tail +N' does not work
##                                        on Ubuntu Karmic (9.10).
##                                        Used a 'tail -N' technique to get
##                                        all but the first 100 recs in a
##                                        time-sorted list of the temp files.
## Updated by: Blaise Montandon 2010sep06 Touched up comments for an initial
##                                        release of the FE 'xpg' subsystem. 

TEMPDIR="/tmp"

## MAKE $USER SUBDIRECTORY of $TEMPDIR, if it does not exist.

if test ! -d  $TEMPDIR/$USER
then
   mkdir $TEMPDIR/$USER
   chmod 777 $TEMPDIR/$USER
   ## We want to allow other users of the computer to delete
   ## 'temp' files created in this user subdirectory of /tmp.
fi

## MAKE NAME OF (TEMPORARY,SCRATCH) LIST FILE.
## Include a time-stamp to avoid overlaying other temp.lis files being
## currently viewed (and perhaps printed). 

SECS=`date +%S`
MINS=`date +%M`
TEMP_FILENAME="temp.lis${MINS}${SECS}"


## MAKE THE FULLNAME OF THE LIST FILE --- in $TEMPDIR/$USER
## if it exists, otherwise in $HOME/tmp.  The filename is in var OUTLIST.
##
## By calling this script with 'dot' ---  . $FEDIR/scripts/set_localoutlist
## --- the OUTLIST var is available to the calling script.

if test -w  $TEMPDIR/$USER
then
   OUTDIR="$TEMPDIR/$USER"
   OUTLIST="$OUTDIR/$TEMP_FILENAME"
else
   echo "
Could not find or create directory  $TEMPDIR/$USER
with write permission for $USER. 
Using $HOME/tmp for temporary list file.
"
   OUTDIR="$HOME/tmp"
   if test ! -d  $OUTDIR
   then
      mkdir $OUTDIR
      chmod 755 $OUTDIR/$USER
   fi
   OUTLIST="$OUTDIR/$TEMP_FILENAME"
fi



## FOR TESTING:
#   set -x

## AT this point $OUTLIST is set.  Now cleanup old & big 'temp.lis####' files
## ... but do it only about half or 1/4 the time, based on current SECS.

# if test \( $SECS -gt 10   -a    $SECS -lt 41 \)
# if test $SECS -lt 15
if test $SECS -lt 30
then

   ##########################################################################
   ## Do cleanup in two sections:
   ## 1) Use 'ls -t' to remove all but N such files , where N=50, say.
   ## 2) Use 'ls -l' to remove temp files that are 'big', say bigger than 50 Meg.
   ##########################################################################



   ##########################################################################
   ## REMOVE ALL BUT 'N-most-recent'  'temp.lis####' FILES:
   ## 
   ## to help avoid accumulation of up to 60x60=3600 temp.list files
   ## in  $TEMPDIR/$USER or $HOME/tmp.
   ##########################################################################
   ## We used to use the 'find' command, but it is difficult to keep
   ## the find command from traveling through subdirectories of
   ## $TEMPDIR/$USER or $HOME/tmp --- in a way that avoids using 'cd'.
   ##########################################################################
   ## We will use 'ls -t' and 'grep' and 'tail' once ---
   ## and we use 'rm' in a 'for' loop, even though we would
   ## like to avoid the 'for' loop.   Since there will generally be less
   ## than 100 files, the loop should always go quickly.
   ##########################################################################
   ## We can test with 'ls -l' in place of 'rm -f'.
   ##########################################################################

     NFILES_KEEP=100
   # NFILES_KEEP=50

   NFILES_TAIL=`expr $NFILES_KEEP + 1`

   ## The +N feature of 'tail' is not working, on Ubuntu Karmic (9.10).
   ## Getting the following error msg:
   ##     tail: cannot open `+101' for reading: No such file or directory
   # TEMPLISFILES=`ls -t $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*' | tail +$NFILES_TAIL -`

   ## An alternative technique:

   TOTFILES=`ls -t $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*' | wc -l`

   if test $TOTFILES -gt $NFILES_KEEP
   then

      FILES2RM=`expr $TOTFILES - $NFILES_KEEP`

      TEMPLISFILES=`ls -t $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*' | tail -$FILE2RM`

      for RELFILE in $TEMPLISFILES
      do
      ## FOR TESTING:
      #   ls -l $OUTDIR/$RELFILE
      #   echo "NOT a MOST-RECENT-${NFILES_KEEP} FILE."

      rm -f $OUTDIR/$RELFILE
      done

    fi



   ##########################################################################
   ## REMOVE BIG 'temp.lis####' FILES:
   ##########################################################################
   ## As above, we avoid using a 'find' command with the '-size' parm.
   ##########################################################################
   ## We will use 'ls' and 'grep' once and then we use 
   ## both 'ls -l' and 'rm' in a 'for' loop, even though we would
   ## like to avoid the 'for' loop.   Since there will generally be less
   ## than 100 files, the loop should always go quickly.
   ##########################################################################
   ## We can test with 'ls -l' in place of 'rm -f'.
   ##########################################################################

   ######################
   ## Define Big = 10 Meg
   ######################
   # BIGinBYTES=10000000

   ######################
   ## Define Big = 50 Meg
   ######################
     BIGinBYTES=50000000

   ## FOR TESTING: (10 KB)
   #  BIGinBYTES=10000

   TEMPLISFILES=`ls $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*'`


   for RELFILE in $TEMPLISFILES
   do
      ## FOR TESTING:
      #  set -x

      RELFILE_BYTES=`ls -l $OUTDIR/$RELFILE | awk '{print $5}'`

      if test $RELFILE_BYTES -gt $BIGinBYTES
      then

         ## FOR TESTING:
         #   ls -l $OUTDIR/$RELFILE
         #   echo "BIG FILE."

         rm -f $OUTDIR/$RELFILE

      fi

      ## FOR TESTING:
      #  set -
   done

fi
## END OF  if test $SECS -lt 30

 Code for the 'xpg' script:

#!/bin/sh
##
## SCRIPT: xpg
##
## PURPOSE: This shell script starts up a GUI utility, nicknamed 'xpg', to
##          browse AND/OR search AND/OR extract-match-lines AND/OR
##          print a text file.
##
## FURTHER DESCRIPTION:
##          This script calls Tcl-Tk script 'shofil.tk' to present the GUI
##          and to do browse actions on the text file --- per user actions on
##          the widgets of the GUI.
##
##          By default, this script starts 'shofil.tk' in the BACKGROUND.
##          (Uses an '&' with 'shofil.tk' for BACKGROUND execution.)
##          But the user can supply a '-f' parameter to this script, to
##          force FOREGROUND execution of 'shofil.tk'.
##
## 'shofil.tk' is basically an X-GUI-version of the Unix (SGI-IRIX)
## 'pg' command. The 'pg' command offered a way to 'page' back and forth
## through a text file in a Unix virtual terminal. The 'pg' command
## was similar to the 'more' and 'less' command --- a big step up from
## using the 'cat' command to browse a text file. 'cat' did not have an
## option to page back (or forward) in the text.
##
## 'shofil.tk' adds a unique extract-matching-lines facility as well as
## built-in print function --- in addition to browse & search functions
## similar to those of the non-GUI 'pg' command (which is like 'less')
## --- but the 'xpg/'shofil.tk' browse & search options are much nicer
## (easier to use, faster, feature-rich).
## CALLED BY:  Can be called  manually at a Unix shell prompt.
##             Example:   xpg <text-filename>
##
## Also could be called from within shell or Tcl-Tk scripts.
##
## One of many ways the 'xpg' script could be implemented on Linux/Unix/BSD:
##
##     Each user could implement 'xpg' via a shortname alias
##     put in their $HOME/.bashrc or $HOME/.bash_aliases
##     or $HOME/.kshrc or $HOME/.profile (or whatever) file, like
##
##         alias xpg='$DIRxpg/scripts/xpg'
##
##     where $DIRxpg represents the install directory of the 'xpg'
##     system files:
##        Tk scripts:
##                    shofil.tk
##                    sho_colorvals_via_sliders3rgb.tk
##                    select_font.tk
##     Shell scripts:
##                    xpg
##                    findANDshow_NOTstringsINfile.sh
##                    findANDshow_stringsINfile_plusminusNlines.sh
##                    set_localoutlist.sh
##         Help file:
##                    shofil.hlp  (plain text)
##
##     The directory DIRxpg could be a subdirectory of the user's
##     home directory --- example:
##          $HOME/apps/xpg
##
##     Alternatively, a link to that 'xpg' script could be used.
##     Example: $HOME/apps/bin/xpg
## This 'xpg' script was adapted from the 'xpg' script of the 'FE xpg'
## subsystem, which is provided, along with other FE subsystems, at
## www.freedomenv.com.  FE = Freedom Environment
## FE system Copyright 2006+ by Blaise Montandon
## MAINTENANCE HISTORY of this 'xpg' script for posting at wiki.tcl.tk:
##
## Written by: Blaise Montandon 2013aug05 Started based on the 'xpg' script
##                                        of the 'FE xpg' subsystem which is
##                                        packaged at www.freedomenv.com.
## Updated by: Blaise Montandon 20.......

## Set the installation directory of the set of 'xpg' files.

# DIRxpg="$HOME/apps/xpg"
  DIRxpg="."


## Set USAGE_TEXT var.

SCRIPTBASENAME=`basename $0`

USAGE_TEXT="Usage:  $SCRIPTBASENAME  [-h] [-f] file1 [ file2 ... file8 ]
-h - gives this help
-f - to execute this utility in the foreground, rather than background."


## If this script was not executed on the command line and
## if no vars (filenames) were passed, pop a msg (using
## the 'zenity' utility) and exit.

## FOR TESTING:
#  env > $HOME/env.lis

if test "$_" = "" -a "$1" = ""
then
   zenity --info --title "xpg: Need filename." \
      --text "\
No filename was passed to the 'xpg' script.

$USAGE_TEXT

Exiting."
    exit
fi

## Otherwise, if this script IS executed on the command line
## and if no vars (esp. filenames) were passed, echo a msg and exit.

# if test $# -eq 0
if test "$1" = ""
then
   echo "
$USAGE_TEXT
"
   exit
fi


## Process 'xpg' command options with 'getopts'.
## '-q' (for QUIET) is not used for now. Maybe someday.

QUIET=false
FOREGROUND=false
while getopts qhf OPTION
do
   ## FOR TESTING
   # echo "OPTION: $OPTION"

   case $OPTION in
   q)
      QUIET=true
      ;;

   f)
      FOREGROUND=true
      ;;


    h|?)
      echo "
$USAGE_TEXT
"
      exit
      ;;

   esac
done
shift `expr $OPTIND - 1`


## STARTUP THE 'xpg' GUI --- using a loop.
## A max of 8 files is accepted --- to prevent
## starting up a huge bunch of instances. Who hasn't done that?
## NOTE: We are using a 'for' loop --- WITHOUT 'in' --- to handle getting
##       filenames --- even filenames with embedded spaces. Ref: man bash

## FOR TESTING:
#   set -x


## These vars can be changed to set the initial location
## and size of the shofil.tk window(s).
##   USERS MIGHT WANT TO RESET THESE TO SUIT THEIR OWN
##   PREFERENCES.

# export WINLOCX="80"
# export WINLOCY="80"
# export WINSIZEX="750"
# export WINSIZEY="450"


## CNT is used in the loop below to delay starting subsquent
## instances of shofil.tk.
## CNT is also used to limit input filenames to 8.

CNT=0

## The LOOP to display the files, with shofil.tk, for the
## filenames passed to this 'xpg' script. (max of 8 files used)

for FILE
do

   ## FOR TESTING:
   # echo "
   # FILE = $FILE"

   if test $CNT = 0
   then
      CNT=`expr $CNT + 1`
   else
      ## Pause before starting subsequent instances,
      ## to make sure they are noticed.
      sleep 1
      CNT=`expr $CNT + 1`
   fi


   ## FOR TESTING:
   # set -x

   if test "$FOREGROUND" = "false"
   then
      $DIRxpg/shofil.tk "$FILE" &
   else
      $DIRxpg/shofil.tk "$FILE"
   fi

   ## FOR TESTING:
   # set -

   if test $CNT -ge 8
   then
      exit
   fi

done


POTENTIAL ENHANCEMENTS:

As I mentioned above, I have been using the 'xpg' utility for several years now and I have not found a need to add new features.

So I have no plans to make any enhancements at this time.

However, in the process of making a 'stand-alone' 'xpg' system from the 'integrated' 'FE xpg' system (with its Tk 'include' files making for even more files in the installation), I must confess that when I got all the features working, I had run out of energy to go through the procs section of the 'shofil.tk' script and clean up the procs and improve their organization (their order).

Also I ran out of steam to go through the comments of the 2 'find' scripts and the 'set_localoutlist.sh' script.

So, someday, when I have more energy, I may return to this page and try to touch up the code --- mainly the comments and the order of the procs.

Meaning of the "stand-alone" terminology

In making this 'stand-alone' version of 'xpg' from the 'integrated' version in the 'FE xpg' subsystem, I merged the code from about five Tk 'include' files that I used to set various GUI parameters, such as colors, fonts, and widget geometry (padding and border-widths).

Those '.tki' Tk include files were used, via Tcl 'source' statements, to provide a consistent user interface for the 3 main Tk scripts in the 'FE xpg' subsystem --- the 'shofil.tk' script and the scripts for the color-selector and the font-selector.

Hence this set of 'xpg' files is 'stand-alone' in the sense that when the color-selector or font-selector GUI's pop up, they may not have the same colors and fonts and widget-geometry as the 'shofil.tk' GUI.

But you can easily make the 3 GUI's look alike by editing the 3 Tk scripts to change, for example, the 'tk_setPalette' statement to give all 3 GUI's a common color.

Here is a situation in which my 'canonical' form for all three scripts comes in handy. The color-setting, font-setting, and widget-geometry setting code is located in the same area in all three scripts --- near the top of the code (or, sometimes, in the 'Additional GUI Initialization' section at the bottom of the code).

THE HELP FILE:

There is one more file to this installation that is not on this page --- the help file. This 'xpg' utility has a lot of operational features that need some explaining. I provide the help file on a separate page --- Help file for the 'xpg' file browser utility. Methods of setting up the 'xpg' command for execution are described in that help file.

In that help and in the 'shofil.tk' code above, it is assumed that the following 8 files of the 'stand-alone' 'xpg' system are installed, together, in a single directory --- such as $HOME/apps/xpg --- not in several separate sub-directories, as they are in the 'integrated' 'FE xpg' system.
   shofil.tk
   sho_colorvals_via_sliders3rgb.tk
   select_tkFont.tk

   findANDshow_NOTstringsINfile.sh
   findANDshow_stringsINfile_plusminusNlines.sh
   set_localoutlist.sh
   xpg

   shofil.hlp

IN CONCLUSION

Pardon me for posting all this code here --- BUT I think we Tcler's need some examples of very useful code that can help us counter the claims of some nattering nabobs of negativism that 'Tcl-Tk is dead'.

Tcl-Tk not dead for me. I hope it is not dead for you. If you could use the 'ShowAllMatches' capability of this 'xpg' utility, then I think you will find that, indeed, Tcl-Tk is not dead for you.

As I have said on at least one other code-donation page on this wiki ...

A BIG THANK YOU to Ousterhout for starting Tcl-Tk, and a BIG THANK YOU to the Tcl-Tk developers and maintainers who have kept the simply MAH-velous 'wish' interpreter going.

RLE (2013-08-06):

In your code above where you strip the leading zero from the "seconds" value obtained from clock format you could replace the entire code block with the following:
 set secs [ scan $secs %d ]

This is documented on the scan page.

However, for your use, which is obtaining a slightly random value using current time as a source, you can also replace the entire code block with the following to avoid all the issues with leading zeros:
 set secs [ expr { [ clock seconds ] % 60 } ]

uniquename 2013aug06: Thanks for the note. I had a feeling that there was a more compact way to do that formatting, but I had so many other issues to address, I had to adopt a 'whatever works' philosophy for that issue.

I will make the changes in a copy of the code that I use everyday, and post changes at a future date. (By the way, sometimes I return to a posting of my code and have so many little changes that it is easier for me to simply overlay the entire code contribution. So if you or anyone makes changes to the code above, those changes may be overlaid at a later date. So it is best to explain the suggested changes as you have done here.)

uniquename 2013aug08: I thought a little more about the code using the current seconds to adjust the location of the 'shofil.tk' window each time it pops up. I am using numbers between 0 and 59 to set a number of pixels for the adjustment. However, whenever 0 is returned there is no adjustment --- and when the seconds are only 1 or 2 or 3, there is hardly any visible difference of a 'shofil.tk' window from a 'shofil.tk' window beneath it. So, when I get back to touching up that little bit of code, I will probably do something like
   set secs [expr {$secs + 10}]

so that I am using numbers between 10 and 69 (pixels) --- rather than between 0 and 59 --- to place a new 'shofil.tk' window.

(Actually an adjustment of 20 pixels, rather than 10, may be better in this era in which we are seeing a transition to monitors with 'retina display' resolutions of about 2000-plus pixels by 1400-plus pixels.)

(And, Tk offers us Tcler's the option to use units other than pixels, like inches or millimeters.)

By the way, this code section brings up why I don't mind writing several lines of code rather than trying to write 'one-liners'.

Often, I like to put some comments explaining why I did various things that might be embedded rather cryptically in a one-liner. Implementing the code in several lines allows me to easily comment the code --- and it is much more readable/understandable to me ('at a glance') if/when I look at the code months later --- and it will probably be much more readable and understandable for other Tcler's.

If the code executes in only a few micro-seconds (and if it is not in a loop that is executed many 100's of times), then there is no significant harm in taking this approach.

I think Ousterhout had some comments to this effect in a coding-philosophy document of his from back in the 1990's. If I can find the quote(s), I will post them here.

RLE (2013-08-08): I generally agree with your sentiment about "cryptically" embedded. However, I was not recommending one line because it was "one line" but because, in my opinion, the one line was significantly more understandable as to the true intent of the code. In your instance above, I'd see the following math expression:
  set offset [ expr { [ clock seconds ] % 60 + 20 } ]

As far more understandable as to the intent. Generate a number ranging between 20 and 79 using the current "seconds" value as a source, rather than this:
  set secs [ clock seconds ]
  set secs [ expr { $secs % 60 } ]
  set secs [ expr { $secs + 20 } ]

In this instance, I find the "spread apart" version less understandable. In other situations, spreading apart the steps is indeed helpful in understanding, I just do not feel that this particular instance is a good example for "spreading apart".

uniquename 2013aug08: As the French say, too-shay (phonetically speaking). I think you are quite right in this case. I could put comments about 'why the 20?' above that single line --- and for 'newbie' Tcler's, comments on 'why the percent sign and the 60?'.