tkAniGIFsPlayer - to Batch-Select-and-Play Animated-GIF files in a Directory Hierarchy

uniquename - 2013may20

Introduction

In the past six months (in Dec 2013 - March 2014), I contributed code to 'batch-find-and-play' and to 'play-from-a-playlist' either

  • image files (JPEG, PNG, GIF, etc.)

or

  • media files (movie or audio files)

The code for these utilities is at the wiki.tcl.tk pages titled:

A couple of months ago (2014 March), I contributed code to 'play-from-a-playlist' --- animated GIF files --- at the wiki.tcl.tk page titled:

I had skipped providing a 'batch-find-and-play' utility for animated GIF files. That is the subject of this page.


COMPARISON of this utility (to the 'batch-find-and-play' utilities mentioned above)

Like the 'batch-find-and-play' utilities for image files and for media files, I wanted to provide a Tk GUI 'front end' to a combination of the 'find' command and a user-selectable 'player' command/program.

In this case, the GUI was designed to offer the user a choice from two animated-GIF-file 'player' programs --- ImageMagick 'animate' and the 'gifview' program that usually comes packaged with the 'gifsicle' utility that can be used to make animated GIF files.

---

Like the 'batch-find-and-play' utilities for image files and for media files, this 'batch-find-and-play' utility for animated-GIF files has been implemented as a Tk script and 2 shell scripts.

As such, it should be implementable with very few changes in various Linux/BSD/Mac/Unix operating system environments.


The GUI LAYOUT

Like for the 'find-and-image-viewer' and 'find-and-media-player' utilities, I made a 'text-sketch' for the GUI for the 'find-and-aniGIF-player' utility:

 ---------------------------------------------------------------------------
 'find'-and-Animated-GIF-Viewers  Front End -  to find-and-view Animated GIFs
 [window title]
 ---------------------------------------------------------------------------------------

 {Exit} {Help} {LaunchPlayerJob} {Next N} {Stop (no more files)}    {CountAniGIFs} {ShowAniGIFfilenames}

 Full Filename Mask (for ani-GIFs): _______________________________________  {Browse...}

 Player Program:   O 1 - ImageMagick 'animate'   O 2 - Gifsicle 'gifview' 

 LayoutOnScreen: O 1x1(1)   O 2x1(2)  O 3x1(3)  O 2x2(4)  O 3x2(6)  O 3x3(9)  O 4x3(12)

 Search Levels for Mask:  O ONE  O ALL subdirectories of selected dir

 Case Sense for Mask Search:  O case-sensitive  O case-INsensitive

 Files Size (KiloBytes):  _____  O bigger-than  O smaller-than

 Files Age (Days) : ______ O older-than  O younger-than

 File Type: ___________   Example: 'GIF' or 'GIF image data' or 'version 89a'
 ----------------------------------------------------------------------------------------

where

 SQUARE BRACKETS indicate a comment (not to be placed on the GUI).
 BRACES          indicate a Tk 'button' widget.
 UNDERSCORES     indicate a Tk 'entry' widget.
 A COLON         indicates that the text before the colon is on a 'label' widget.
 CAPITAL-O       indicates a Tk 'radiobutton' widget.
 CAPITAL-X       indicates a Tk 'checkbutton' widget (if any).

NOTE that only at the 'Player Program:' and 'LayoutOnScreen:' prompts are there parameters involving the aniGIF-player.

The other parameters (the majority of the parameters) are for the 'find' command.

---

GUI Components

From the GUI 'sketch' above, it is seen that the GUI consists of about

   -  8 button widgets
   -  9 label  widgets
   -  4 entry  widgets
   - 17 radiobutton widgets in 6 groups
   -  0 checkbutton widgets
   -  0 scale widgets
   -  0 listbox widgets
   -  0 text widgets
   -  0 canvas widgets

There is one line added to this GUI, compared to the GUI for the 'find-and-image-viewer' utility:

   - a 'LayoutOnScreen' line, to allow for playing a 'group' of
     animated GIF files 'at a time' on the screen.

---

Front-End-for-Two

This 'Front End' Tk script is essentially a 'wrapper' for TWO commands --- the 'find' command that assembles the files to be 'played' and a 'player' command (of the user's choice, from among more than one).

NOTE that among the 8 'parameter-lines' of this GUI, only 2 have to do with the animated-GIF 'player':

  • the line of radiobuttons used to select the 'player'

and

  • the 'LayoutOnScreen' line (1x1 , 2x1 , 3x1 , 2x2, ... ).

The other parameters (on 6 of the lines) are for the 'find' command.

---

The Players

As far as choosing which animated-GIF player to use, both 'animate' and 'gifview' have their idiosyncracies. The question of which one to use may come down to which one is already available --- or which one is easier to install on the user's operating system.

To avoid surprises, it may help for the user to know that if they click on an 'animate' window, a menu of options pops up. Typically, I find that when that happens, I simply want to close that popup window.

---

GUI Beauty (or lack thereof)

I should point out here that I was not especially interested in coming up with a 'beautiful utility'. I just wanted a utility that would be able to sweep through a hierarchy of sub-directories and pick out animated-GIF files to 'play' based on a rather 'rich' set of selection capabilities --- such as 'file-mask' and/or 'file-size' and/or 'file-age'.

I am certainly interested in making pretty GUI's --- as I have expressed on my 'bio' page at uniquename, and as my pages at Experiments in making embellished GUI's and Version 2 of a demo of THEMES for Tk GUI's, using images and colors have indicated.

But at this time, I am satisfied to implement the 'functionality', and let the 'beauty' go for a later date (when I have more beauty tools/code at hand).


A GUI IMAGE (and some discussion of how-to-use)

On the basis of the sketch above, I ended up with the GUI seen in the following image.

tkBatchAniGIFsPlay_FrontEnd_allSubDirs_screenshot_716x287.jpg

Note that there are a couple of radiobuttons that allow you to choose whether application of the file-mask is 'case-sensitive' or 'case-INsensitive'. For example, if you chose 'case-INsensitive' and the mask were set to '*.gif', then files with the suffix '.GIF' would also be selected.

And there are a couple of radiobuttons that allow you to choose whether to do the 'mask-search' at the current directory level only --- or to sweep through all sub-directories of the current 'base' directory, looking for animated-GIF files that satisfy the criteria chosen via this GUI.

To help avoid accidentally triggering a 'find' search on a 'deep' directory (full of many, many files), the GUI is intially defaulted to a ONE-level search. If the user is disappointed by a search that yielded zero animated-GIF files, the user may want to change the search to 'ALL-subdirectories'.

Here is a place where I MAY make a future enhancement: I may change the 'ONE' radiobutton to an 'N-levels' radiobutton --- and if that radiobutton is selected, an entry field will be activated where the user can enter a choice of N (with the entry field being initialized with '1').

---

NOTE that most simple image-file 'players' are oriented toward 'playing' the files in a single directory.

This 'find-and-aniGIF-play' utility is oriented toward SELECTING 'aniGIF' files (based on criteria such as a file-mask, file-size, and/or file-age) from an ENTIRE HIERARCHY OF SUB-DIRECTORIES --- as well as allowing mask-search in a SINGLE directory.

This utility SORTS the found-files by filename and then starts a loop in which 'groups' of aniGIF files are shown by the user-selected 'player'.

So this utility does not have the flexibility of a 'playlist' approach. The user is not given an opportunity to change the order in which the selected files are played. (That capability is offered by the 'play-from-a-playlist' utility that I have already made for aniGIF files.)

On the other hand, this utility offers the user the ability to quickly and auto-magically sweep through a directory hierarchy, selecting files by a 'rich' set of criteria, and 'play' the selected aniGIF files --- which may be scattered throughout that hierarchy.

By using the file-mask option (and file-age and file-size options), the user has quite a bit of selection ability. The user can avoid a lot of 'manual' searching for files by letting the 'find' command find the desired files.

ALSO, note that this utility can be useful without even using any of the 'player' programs. Say you want to know the NUMBER or NAMES of the animated-GIF files satisfying a set of criteria (file-mask, file-size, file-age). Then set the criteria and click on the 'CountFilenames' or 'ShowFilenames' button.

IN FACT, by using the 'ShowFilenames' button you can get a list from which to build a 'playlist' file. The filenames are shown in a popup Tk 'text' widget --- from which you can paste the names into a text-editor window of your choice.

---

Note also that this utility can be used to 'play' a single file. Just select a full filename and do not change the last part of the filename to a mask. Then click the 'LaunchPlayer' button. (Remember to unset any criteria which the selected file would not satisfy. Note that you can click on the 'CountFilenames' button to see if it returns a value of 1, rather than 0.)


DESCRIPTION OF THE CODE

Below, I provide the Tk script code for this 'multi-subdirectory tkBatchAniGIFsPlayer' utility.

I follow my usual 'canonical' structure for Tk code for this 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 thing that I started doing in 2013 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 pretty nice choice of the 'pack' parameters. The label and button and radiobutton widgets stay fixed in size and relative-location if the window is re-sized --- while the entry widgets expand/contract horizontally whenever the window is re-sized horizontally.

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.

___

Additional experimentation: 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.

If you find the gray palette of the GUI is not to your liking, you can change the value of the RGB parameter supplied to the 'tk_setPalette' command near the top of the code.


Some features in the code

There are plenty of comments in the code, 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 main procs are

  'get_filemask'             - called by the 'Browse...' button

  'get_chars_before_last'    - called by proc 'get_filemask'

  'check_for_criterion_change' - called by the following
                                 'findAniGIFs_forCriteria' proc

  'findAniGIFs_forCriteria'    - called by several procs below, namely
                                 - 'count_aniGIF_files'
                                 - 'show_aniGIF_filenames'
                                 - 'play_nextN_aniGIF_files' 

  'count_aniGIF_files'       - called by the 'CountAniGIFs' button.

  'show_aniGIF_filenames'    - called by the 'ShowAniGIFfilenames' button.

  +-------------------------------------
  For viewing the 'found' aniGIF files:

  'set_player_command'       - called by button1-release bindings on the
                               player radiobuttons.

  'start_playing_aniGIF_files' - called by the 'LaunchPlayerJob' button.

  'play_nextN_aniGIF_files'    - called by the 'start_playing_aniGIF_files'
                                 proc and the 'Next N' button.

  'set_window_positions'     - called by 'play_nextN_aniGIF_files' proc
                               to set the window positions according
                               to the current user-selected windows-layout.
  +-------------------------------------

   'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
                                         Also used via the 'CountFilenames'
                                         and 'ShowFilenames' buttons.

Like with the 'find-and-imageViewer' Tk GUI utility, I used the following statement to allow the GUI to be expanded in the x-direction, but NOT the y-direction.

   wm resizable . 1 0

Lessons Learned (and incorporated in this utility)

In using the 'tkBatchMediaPlayer' (for movie/audio files) on 'deep' directories --- that is, a 'find' search on about 200-plus subdirectories with 2,000-plus files in those subdirectories --- I found a few improvements that I needed to make in that Tk script and its 2 associated shell scripts.

I found the following 'needs'.

1) When I used the 'CountFilenames' and 'ShowFilenames' and 'LaunchPlayerJob' buttons with the same 'find' criteria, the same long 'find' search would have to be repeated --- unnecessarily. I needed to save the 'found-filenames' in an output file, and work off of that list of files when the 'find' criteria had not been changed by the user.

2) On a long 'find' search, a 'progress indicator' was needed to let the user know if the 'find' command was doing its job --- and to let the user know how far/fast the search was proceeding.

To handle these needs, I put the following features into the 'find-aniGIFs' utility.

*** In the Tk script, I set the filename of a temporary file to be used to hold the names of animated-GIF files found by the 'find' search --- in variable 'outFILE'.

*** In the Tk script, I added 'PREV' variables to be used to hold about 8 previous 'find' search criteria --- to be used to indicate when the user has changed a 'find' criterion.

*** In the Tk script, I created a proc called 'check_for_criterion_change' --- to determine if a criterion variable has been changed by the user.

*** In the Tk script, I run the 'findAniGIFfiles_forCriteria.sh' script only if one of the 'find' search criteria was changed by the user.

*** In the Tk script, I provided an 'xterm' window to the 'exec' statement for the 'findAniGIFfiles_forCriteria.sh' shell script, to allow for better visibility of how a lengthy 'find' is progressing. The 'find' command in the 'findAniGIFfiles_forCriteria.sh' shell script was devised to show directories being searched, as well as files being found, in order to provide a better 'progress indicator'.


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 that video of a Giant Panda pulling the jacket off of a guy who sat down against the fence enclosing the panda's turf.


 Code for Tk script 'find_and_AniGifViewers_FrontEnd.tk' : (the 'front end')

#!/usr/bin/wish -f
##
## Tk SCRIPT NAME: find_and_AniGifViewers_FrontEnd.tk
##
##+#######################################################################
## PURPOSE:  This Tk script provides a GUI for
##           1) finding a 'batch' of animated-GIF files according to
##              user-specified criteria that are prompted for via
##              entry widgets and radiobutton widgets on the GUI,
##           AND
##           2) starting up an animated-GIF file viewer program --- like
##              the ImageMagick 'animate' program or the 'gifview' program
##              that comes with the 'gifsicle' program --- to view the 'batch'
##              of animated-GIF files that were found, via the 'find' command.
##
##           Hence, the GUI is a 'front-end' for the 'find' command and
##           an animated-GIF viewing command.
##
##           The GUI is intended to make the user aware of the main
##           (most useful) options available without the user needing
##           to reference the 'man find' command and 'man animate' or
##           'man gifview' commands --- nor other 'find'/'animate'/'gifview'
##           documentation, for example, via web searches.
##
##           The intent of this utility is to allow the user to
##           easily specify files to be viewed --- via specifying:
##            1  - a directory-and-filename (the latter is typically a 'mask'),
##            2  - whether files in sub-directories should be mask-searched,
##            3  - whether the mask-search should be case-INsensitive,
##            4  - whether to use a file-size limitation,
##            5  - whether to use a file-age limitation,
##            6  - which of multiple animated-GIF viewer programs to use,
##            7  - a layout for 1 or more viewer windows on the screen ---
##                 say 1x1, 2x1, 2x2, 3x2, 3x3, 4x3, or whatever.
##                 (Since animated-GIF files are generally rather small,
##                  we allow for showing/reviewing more than one at a time.)
##
##           NOTE that most image-file viewers (animated or not) are oriented
##           toward scanning through ALL image files in a SINGLE directory.
##
##           This utility is oriented toward SELECTING image files (based
##           on criteria such as a file-mask, file-size, and/or file-age)
##           from an ENTIRE HIERARCHY OF SUB-DIRECTORIES --- as well as
##           allowing mask-search in a SINGLE directory.
##
##           Although the 'find' command does not sort the filenames nicely,
##           this utility SORTS the selected files ACCORDING TO their 
##           full filenames and then starts playing the files. 
##
##+################
## THE GUI WIDGETS:
##
##           The GUI offers a generous set of user specifications.
##           The options available to the user are compactly indicated
##           by the following 'text-sketch' of the GUI:
##
## ---------------------------------------------------------------------------
## 'find'-and-Animated-GIF-Viewers  Front End -  to find-and-view Animated GIFs
## [window title]
## --------------------------------------------------------------------------------
## 
## {Exit} {Help} {LaunchPlayerJob} {Next N} {Stop (no more files)}    {CountAniGIFs} {ShowAniGIFfilenames}
## 
## Full Filename Mask (for ani-GIFs): _______________________________________  {Browse...}
## 
## Player Program:   O 1 - ImageMagick 'animate'   O 2 - Gifsicle 'gifview' 
## 
## LayoutOnScreen: O 1x1(1)   O 2x1(2)  O 3x1(3)  O 2x2(4)  O 3x2(6)  O 3x3(9)  O 4x3(12)
## 
## Search Levels for Mask:  O ONE  O ALL subdirectories of selected dir
## 
## Case Sense for Mask Search:  O case-sensitive  O case-INsensitive
## 
## Files Size (KiloBytes):  _____  O bigger-than  O smaller-than
## 
## Files Age (Days) : ______ O older-than  O younger-than
##
## File Type: ___________   Example: 'GIF' or 'GIF image data' or 'version 89a'
## ---------------------------------------------------------------------------------------------------------------
##
##+##################################################################
## DESCRIPTION OF THE DRAWING CONVENTION used in the GUI sketch above:
##+##################################################################
##
## SQUARE BRACKETS indicate a comment (not to be placed on the GUI).
## BRACES          indicate a Tk 'button' widget.
## UNDERSCORES     indicate a Tk 'entry' widget.
## A COLON         indicates that the text before the colon is on a 'label' widget.
## CAPITAL-O       indicates a Tk 'radiobutton' widget.
## CAPITAL-X       indicates a Tk 'checkbutton' widget (if any).
##
## NOTE that only at the 'Player Program:' and 'LayoutOnScreen:' prompts
## are there parameters involving the aniGIF-player.
##
## The other parameters (the majority of the parameters) are for
## the 'find' command.
##
##+##############
## GUI components:
##
## From the GUI 'sketch' above, it is seen that the GUI consists of
## about
##   
##   -  8 button widgets
##   -  9 label  widgets
##   -  4 entry  widgets
##   - 17 radiobutton widgets in 6 groups
##   -  0 checkbutton widgets
##   -  0 scale   widgets
##   -  0 listbox widgets
##   -  0 canvas  widgets
##   -  0 text    widgets
##
##+#########################
## TYPICAL OPERATIONAL STEPS:
##
##           After specifying/selecting the 'find' parameters and specifying
##           the 'player' and 'layout', then the user can click on a 'Launch'
##           button to start playing the animated GIF files --- after the
##           filenames are found and the names put in a 'temporary' file.
##
##           The first N files are played, where N is determined by the
##           'layout' that was selected.
##
##           To play the next N files, the user clicks on the 'Next N' button.
##
##           To discontinue playing any more files from the 'found-filenames'
##           file, the user can click on the 'Stop' button.
##
##           The user can also click on the 'CountAniGIFs' and the
##           'ShowAniGIFfilenames' buttons to get a view of the contents
##           of the 'found-files' file.
##
##           The user can click on the 'Help' button for a description
##           of this utility (similar to this description).
##
##+#################################################
## NOTES ON PLAYING/STOPPING THE ANIMATED GIF FILES:
##
##    Note that this utility is designed with the option to show
##    multiple animated-GIF files at a time on screen --- in 'groups'
##    --- 1x1 or 2x1 or 2x2 or 3x2 or ...
##
##    This utility is designed so that the user can signal when
##    a next 'group' is to be displayed.
##
##    The user has the option to close the currently-open 'player'
##    window(s) before starting the next group. Thus the user can
##    leave some animations running when starting another 'group'.
##
##    The user can 'pull' windows around to display the animations
##    in whatever 'layout' that one desires.
##
##           (Note that most animated-GIF files keep animating until stopped
##           by the user. If a lot of files are to be displayed, the
##           user will probably want to close one group of windows before
##           starting another group.)
##
##+######################
## AniGIF PLAYER PROGRAMS:
##
##    The player program is chosen via radiobutton widgets on the GUI.
##    At least 2 animated-GIF-players are selectable via the GUI.
##
##    Some examples of 'light-weight' 'player' programs:
##           - ImageMagick 'animate'
##           - 'gifview', that comes with the 'gifsicle' program
##                        that can be used to make animated GIF files.
##
##    Other 'aniGIF' players could be added --- or switched for
##    players currently made selectable on the GUI.
##
##    A shell script named 'forLoop_displayAniGIFsInFile.sh' is passed
##    the name (and arguments) of a player program and the name of a
##    temporary file containing the 'found' aniGIF filenames.
##
##    You can examine the code below for where that shell script
##    is called to trace back how you can add/change an aniGIF
##    player program to this utility.
##
##    If only SWITCHING (rather than ADDING) an AniGIF-player,
##    simple changes would be required to the Tk GUI script ---
##    including changing the player-name on the GUI --- in a text
##    string for a 'radiobutton' widget.
##
##    Of course, if you are ADDING an AniGIF-player, you will need
##    to add a 'radiobutton' widget to the GUI.
##
##+-----
## On Using a Web-Browser as an aniGIF Player:
##
##    Almost any web-browser can be passed the name of an image file,
##    such as an animated GIF file, and the browser will play the file.
##    But it typically takes quite a few seconds for the web browser
##    to start up. So we leave it as an option to the user whether
##    they want to add one or more web-browser choices to the GUI
##    --- by adding additional radiobuttons to the GUI.
##
##        (Note that this utility starts an instance of the player
##         program for each aniGIF file being played. This may
##         be a time-and-CPUpower-consuming process for 'heavy-weight'
##         web-browser programs --- unless they offer an option to
##         'fork off' new display windows from an existing instance.)
##
##+###################################################
## DOING COUNT/LIST/PLAY EFFICIENTLY FOR LARGE 'finds':
##
## To avoid re-performing a long-running 'find' command that has been done already
## --- and to provide a 'progress indicator' for a long-running 'find' command:
##
## 1) We set the filename of a temporary file to be used to hold the names of
##    animated-GIF files found by the 'find' in variable 'outFILE'.
##
## 2) We use about seven PREV variables to indicate when the user has changed
##    a 'find' criterion.
##
## 3) We use proc 'check_for_criterion_change' to determine if a
##    criterion variable has been changed.
##
## 4) We use the shell script 'findAniGIFfiles_forCriteria.sh' to perform
##    the 'find' for either 'count' or 'list' or 'play' options --- if
##    there was a 'find' criterion change.
##
## 5) The 'count', 'list', and 'play' options use file '$outFILE' if no
##    criterion was changed --- and they (re)load '$outFILE' if a criterion
##    was changed.
##
## 6) We use an 'xterm' window in the 'exec' statement for the
##    'findAniGIFfiles_forCriteria.sh' shell script, to allow for
##    visibility of how a lengthy 'find' is progressing.
##
##+#####################################################################
## CALLED BY:  This script could be put in a sub-directory of the
##             user's home directory, such as $HOME/apps/tkBatchAniGifPlay.
##
##             Then the user can use their desktop system (such as
##             Gnome or KDE) to set up the Tk script as an icon on the
##             desktop.
##
##             Then the user can click on the icon to startup the
##             Tk script whenever they want to search a directory
##             hierarchy for aniGIF files.
##+########################################################################
## STRUCTURE OF THIS CODE:
##
##  0) Set general window parms (win-name, win-position, win-color-scheme,
##     fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack the frames.
##
##  2) Define & pack all widgets in the frames, frame by frame.
##     After all the widgets for a frame are defined, pack them in the frame.
##
##  3) Define keyboard and/or mouse/touchpad/touch-sensitive-screen 'event'
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically with one or more of
##     the procs), if needed.
##
## In more detail:
##
##  1a) Define ALL frames -- and sub-frames:
## 
##   Top-level :
##       'fRbuttons'       for Exit, Help, Launch, ... buttons
##       'fRfileMask'      for a directory-and-filemask entry field
##       'fRdisplayPgm'    for several radiobuttons, with a label
##       'fRwindowsLayout' for several radiobuttons, with a label
##       'fRlevels'        for 2 radiobuttons, with a label
##       'fRsense'         for 2 radiobuttons, with a label
##       'fRfileSize'      for 1 entry field and 2 radiobuttons, with a label
##       'fRfileAge'       for 1 entry field and 2 radiobuttons, with a label
##       'fRfiletype'      for 1 entry field, with a label before and after 
##
##  1b) Pack ALL frames, including sub-frames (if any).
##
##  2) Define & pack all widgets in the frames -- basically going through
##     frames & their interiors in  left-to-right, top-to-bottom order:
##
##  3) Define bindings:  See BINDINGS section below.
##
##  4) Define procs:
##
##  'get_filemask'             - called by the 'Browse...' button
##
##  'get_chars_before_last'    - called by proc 'get_filemask'
##
##  'check_for_criterion_change' - called by the following
##                                 'findAniGIFs_forCriteria' proc
##
##  'findAniGIFs_forCriteria'    - called by several procs below, namely
##                                 - 'count_aniGIF_files'
##                                 - 'show_aniGIF_filenames'
##                                 - 'play_nextN_aniGIF_files' 
##
##  'count_aniGIF_files'       - called by the 'CountAniGIFs' button.
##
##  'show_aniGIF_filenames'    - called by the 'ShowAniGIFfilenames' button.
##
##+-------------------------------------
## For viewing the 'found' aniGIF files:
##
##  'set_player_command'       - called by button1-release bindings on the
##                               player radiobuttons.
##
##  'start_playing_aniGIF_files' - called by the 'LaunchPlayerJob' button.
##
##  'play_nextN_aniGIF_files'    - called by the 'start_playing_aniGIF_files'
##                                 proc and the 'Next N' button.
##
##  'set_window_positions'     - called by 'play_nextN_aniGIF_files' proc
##                               to set the window positions according
##                               to the current user-selected windows-layout.
##+-------------------------------------
##
##   'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
##                                         Also used via the 'CountFilenames'
##                                         and 'ShowFilenames' buttons.
##
##     For complete list of procs, see the PROCS section below.
##
##  5) Additional GUI initialization:  See this section at the bottom
##                                     of this script.
##+#######################################################################
## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala')
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##
## showed
##     8.5 8.5
## but this script should work in most previous 8.x versions, and probably
## even in some 7.x versions (if font handling is made 'old-style').
##+########################################################################
## MAINTENANCE HISTORY:
## Started by: Blaise Montandon 2014feb24 Started development, on Ubuntu 9.10,
##                                        based on the code of a couple of
##                                        similar Tk scripts of mine:
##                                        'find_and_imageViewer_FrontEnd.tk'
##                                        and
##                                        'playAniGIFfilesInPlaylist_FrontEnd.tk'.
##                                        These 2 Tk scripts contained most of
##                                        the comments,widgets,procs needed.
## Updated by: Blaise Montandon 2014may19 Started preparing the procs for
##                                        testing.
## Updated by: Blaise Montandon 2014may26 Moved some button config statements
##                                        in proc 'start_playing_aniGIF_files'
##                                        so that they do not un-do button config
##                                        statements performed by the proc
##                                        'play_nextN_aniGIF_files'.
##+#######################################################################

##+######################################################
## Set WINDOW TITLE and POSITION.
##+######################################################

wm title    . \
   "'find'-and-Animated-GIF-Player FrontEnd - to BATCH-select & play aniGIFs"

#  "'find'-and-Animated-GIF-Viewers Front End - to find-and-view Animated GIFs"


wm iconname . "BatchAniGIFplay"

# wm geometry . +15+30
# wm geometry . +250+285
wm geometry . -10-10


##+######################################################
## Set the COLOR SCHEME for the window and its widgets ---
## such as listbox and entry field background color.
##+######################################################

tk_setPalette "#e0e0e0"


  set entryBKGD "#ffffff"
  set textBKGD  "#f0f0f0"
  set radbuttBKGD  "#ffffff"
# set chkbuttBKGD  "#ffffff"
# set scaleBKGD   "#f0f0f0"
# set listboxBKGD "#f0f0f0"


##+########################################################
## DEFINE (temporary) FONT NAMES.
##
## We use a VARIABLE-WIDTH font for text on LABEL and
## BUTTON widgets.
##
## We use a FIXED-WIDTH font for LISTBOX lists,
## for text in ENTRY fields --- and often for text in
## TEXT widgets.
##+########################################################

font create fontTEMP_varwidth \
   -family {comic sans ms} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_varwidth \
   -family {comic sans ms} \
   -size -12 \
   -weight bold \
   -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


font create fontTEMP_fixedwidth  \
   -family {liberation mono} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_fixedwidth  \
   -family {liberation mono} \
   -size -12 \
   -weight bold \
   -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


##+###########################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. width and height of canvas, and padding for Buttons)
##+###########################################################

## LABEL widget geom settings:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2
set RELIEF_label_lo "flat"


## BUTTON widget geom settings:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2
## We use '-relief raised' for all 'button' widgets.


## ENTRY widget geom settings:

set BDwidthPx_entry 2
## We use '-relief sunken' for all 'entry' widgets.


## RADIOBUTTON widget geom settings:

set PADXpx_radbutton 0
set PADYpx_radbutton 0
set BDwidthPx_radbutt 2
set RELIEF_radbutt_hi "raised"


## CHECKBUTTON widget geom settings:

# set PADXpx_chkbutton 0
# set PADYpx_chkbutton 0
# set BDwidthPx_chkbutt 2
# set RELIEF_chkbutt_hi "raised"


## TEXT widget geom settings:

set BDwidthPx_text 2
# set RELIEF_numtext "ridge"


## SCALE widget geom parameters:

# set BDwidthPx_scale 2
# set scaleThicknessPx 10


##+##############################################################
## 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 '.fRbuttons' frame:

set aRtext(buttonEXIT)    "Exit"
set aRtext(buttonHELP)    "Help"
set aRtext(buttonLAUNCH)  "LaunchPlayerJob"
set aRtext(buttonNEXT)    "Next"
set aRtext(buttonSTOP1)   "Stop (no more aniGIFs)"
set aRtext(buttonSTOP2)   "Stop indicator was SET"

set aRtext(buttonCOUNT)  "CountAniGIFs"
set aRtext(buttonPRINT)  "ShowAniGIFfilenames"

## For '.fRfileMask' frame:

set aRtext(labelFILEMASK) "Full Filename Mask (for ani-GIFs):"
set aRtext(buttonBROWSE)  "Browse..."


## For '.fRdisplayPgm' frame:

set aRtext(labelDISPLAYPGM)  "Player Program:"
set aRtext(radbuttDPGM1)     "1 - ImageMagick 'animate'"
set aRtext(radbuttDPGM2)     "2 - Gifsicle 'gifview'"
# set aRtext(radbuttDPGM3)   "3 - Seamonkey web-browser"


## For '.fRwindowsLayout' frame:

set aRtext(labelLAYOUT)  "LayoutOnScreen:"
set aRtext(radbuttLAYOUT1x1)  "1x1(1)"
set aRtext(radbuttLAYOUT2x1)  "2x1(2)"
set aRtext(radbuttLAYOUT3x1)  "3x1(3)"
set aRtext(radbuttLAYOUT2x2)  "2x2(4)"
set aRtext(radbuttLAYOUT3x2)  "3x2(6)"
set aRtext(radbuttLAYOUT3x3)  "3x3(9)"
set aRtext(radbuttLAYOUT4x3)  "4x3(12)"

## For '.fRlevels' frame:

set aRtext(labelLEVELS)       "Search LEVELS for mask:"
set aRtext(radbuttLEVELSone)  "ONE"
set aRtext(radbuttLEVELSall)  "ALL subdirectories of selected directory"

## For '.fRsense' frame:

set aRtext(labelCASESENSE) "Case Sense for Mask Search:"
set aRtext(radbuttSENSE)     "case-sensitive"
set aRtext(radbuttNOSENSE)   "case-INsensitive"


## For '.fRfileSize' frame:

set aRtext(labelFILESIZE)   "Files Size (KiloBytes):"
set aRtext(radbuttBIGGER)   "bigger-than"
set aRtext(radbuttSMALLER)  "smaller-than"

## For '.fRfileAge' frame:

set aRtext(labelFILEAGE)  "Files Age (Days):"
set aRtext(radbuttOLDER)  "older-than"
set aRtext(radbuttYOUNGER)  "younger-than"

## For '.fRfileType' frame:

set aRtext(labelFILETYPE)  "Files Type:"
set aRtext(labelFILETYPE2) "Examples: 'GIF'  or  'GIF image data' or 'version 89a'"

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


##+######################################################################
## Set a MIN-SIZE of the window (roughly).
##
## For WIDTH, allow for the min-width of the '.fRbuttons' frame.
##
## For HEIGHT, allow for the stacked frames:
##      1 char   high for the '.fRbuttons'     frame
##      1 char   high for the '.fRfileMask'    frame
##      1 char   high for the '.fRdisplayPgm'  frame
##      1 char   high for the '.fRwindowsLayout' frame
##      1 char   high for the '.fRlevels'      frame
##      1 char   high for the '.fRsense'       frame   
##      1 char   high for the '.fRfileSize'    frame
##      1 char   high for the '.fRfileAge'     frame
##    --------
##      8 chars  high for the 10 frames
##+#####################################################################

## FOR WIDTH: (allow for widgets in the '.fRbuttons' or '.fRfileMask' frame)

set minWidthPx [font measure fontTEMP_varwidth \
   " $aRtext(buttonEXIT) $aRtext(buttonHELP) $aRtext(buttonLAUNCH) \
$aRtext(buttonCOUNT) $aRtext(buttonPRINT) "]

## We add some pixels to account for right-left-size of
## window-manager decoration (~8 pixels) and some pixels for
## frame/widget borders (~5 widgets x 4 pixels/widget = 20 pixels).

set minWinWidthPx [expr {28 + $minWidthPx}]


## For HEIGHT --- 8 chars high for the 8 frames.

set charHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {8 * $charHeightPx}]


## Add about 20 pixels for top-and-bottom window decoration --
## and some pixels for top-and-bottom of frame/widget borders
## (~8 widgets x 4 pixels/widget = 32 pixels).

set minWinHeightPx [expr {52 + $minWinHeightPx}]


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

wm minsize . $minWinWidthPx $minWinHeightPx


## We may allow the window to be resizable.  We pack the canvases
## (and the frames that contain them) with '-fill both -expand 1'
## so that the canvases can be enlarged by enlarging the window.

## If you want to make the window un-resizable, 
## you can use the following statement.
#   wm resizable . 0 0

## We fix the y-size of the window, but allow the x-size to vary.
wm resizable . 1 0



##+####################################################################
##+####################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level :
##       'fRbuttons'     for Exit, Help, and Launch buttons
##       'fRfileMask'    for a filename/mask entry field
##       'fRdisplayPgm'     for several radiobuttons, with a label
##       'fRwindowsLayout' for several radiobuttons, with a label
##       'fRlevels'      for 2 radiobuttons, with a label
##       'fRsense'       for 2 radiobuttons, with a label
##       'fRfileSize'    for 1 entry field and 2 radiobuttons, with a label
##       'fRfileAge'     for 1 entry field and 2 radiobuttons, with a label
##+####################################################################
##+####################################################################

## FOR TESTING of expansion of frames (esp. during window expansion):

# set feRELIEF_frame raised
# set feBDwidth_frame 2

 set feRELIEF_frame flat
 set feBDwidth_frame 0

frame .fRbuttons          -relief $feRELIEF_frame  -bd $feBDwidth_frame

frame .fRfileMask         -relief raised           -bd 2

  frame .fRdisplayPgm     -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRdisplayPgm     -relief raised           -bd 2

  frame .fRwindowsLayout  -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRwindowsLayout  -relief raised           -bd 2

  frame .fRlevels         -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRlevels         -relief raised           -bd 2

  frame .fRsense          -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRsense          -relief raised           -bd 2

  frame .fRfileSize       -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRfileSize       -relief raised           -bd 2

  frame .fRfileAge        -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRfileAge        -relief raised           -bd 2

  frame .fRfileType       -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRfileType       -relief raised           -bd 2


##+########################################################
## PACK *ALL* the FRAMES.
##+########################################################

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

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

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

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

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

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

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

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

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


##+################################################################
##+################################################################
## START DEFINING & PACKING WIDGETS WITHIN THEIR FRAMES. 
##+################################################################
##+################################################################

##+########################################################
## IN THE '.fRbuttons' frame -- DEFINE several buttons
## --- Exit, Help, Launch.
##+########################################################

button .fRbuttons.buttEXIT \
   -text "$aRtext(buttonEXIT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {exit}

button .fRbuttons.buttHELP \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {popup_msgVarWithScroll .topHelp "$HELPtext"}

button .fRbuttons.buttLAUNCH \
   -text "$aRtext(buttonLAUNCH)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -bd $BDwidthPx_button \
   -relief raised \
   -command {start_playing_aniGIF_files}

button .fRbuttons.buttNEXT \
   -text "$aRtext(buttonNEXT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -bd $BDwidthPx_button \
   -relief raised \
   -command {play_nextN_aniGIF_files}

## Start with 'Next' button disabled.

.fRbuttons.buttNEXT configure -state disabled


## Initialize variable for the following 'Stop' button.

set STOPvar0or1 0

button .fRbuttons.buttSTOP \
   -text "$aRtext(buttonSTOP1)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -bd $BDwidthPx_button \
   -relief raised \
   -command {set STOPvar0or1 1 ; \
     .fRbuttons.buttSTOP configure -text "$aRtext(buttonSTOP2)"}

## Start with 'Stop' button disabled.

.fRbuttons.buttSTOP configure -state disabled



button .fRbuttons.buttCOUNT \
   -text "$aRtext(buttonCOUNT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {count_aniGIF_files}

button .fRbuttons.buttPRINT \
   -text "$aRtext(buttonPRINT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {show_aniGIF_filenames}

##+##########################################
## Pack the widgets in the 'fRbuttons' frame.
##+##########################################

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttLAUNCH \
     .fRbuttons.buttNEXT \
     .fRbuttons.buttSTOP \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.buttPRINT \
     .fRbuttons.buttCOUNT \
   -side right \
   -anchor e \
   -fill none \
   -expand 0


##+########################################################
## IN THE '.fRfileMask' frame -- DEFINE 1 LABEL widget,
## 1 ENTRY widget, and 1 BUTTON.
##+########################################################

label .fRfileMask.labelFILEMASK \
   -text "$aRtext(labelFILEMASK)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label

entry .fRfileMask.entryFILEMASK \
   -textvariable ENTRYfileMask \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -relief sunken \
   -bd $BDwidthPx_entry

## Set an initial value for the entry var.

set curDIR "$env(HOME)"

## FOR TESTING:
#   set curDIR [pwd]

set ENTRYfileMask "$curDIR/*.gif"

## Put the end of the filename in view.

.fRfileMask.entryFILEMASK xview end


button .fRfileMask.buttBROWSE \
   -text "$aRtext(buttonBROWSE)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {get_filemask}

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

pack  .fRfileMask.labelFILEMASK \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 

pack .fRfileMask.entryFILEMASK \
   -side left \
   -anchor w \
   -fill x \
   -expand 1 

pack .fRfileMask.buttBROWSE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 


##+########################################################
## IN THE '.fRdisplayPgm' frame -- DEFINE
## several RADIOBUTTONS, preceded by a LABEL widget.
##+########################################################

label .fRdisplayPgm.labelDISPLAYPGM \
   -text "$aRtext(labelDISPLAYPGM)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief  $RELIEF_label_lo

## DEFINE Radiobuttons for display program :

radiobutton  .fRdisplayPgm.radbuttDPGM1 \
   -text "$aRtext(radbuttDPGM1)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARdisplaypgm \
   -value "1" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRdisplayPgm.radbuttDPGM2 \
   -text "$aRtext(radbuttDPGM2)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARdisplaypgm \
   -value "2" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


## RADVARdisplaypgm is the var for these 2 or 3 radiobuttons.
## Set an initial value.
  set RADVARdisplaypgm "1"
# set RADVARdisplaypgm "2"

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

pack  .fRdisplayPgm.labelDISPLAYPGM \
      .fRdisplayPgm.radbuttDPGM1 \
      .fRdisplayPgm.radbuttDPGM2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 


##+########################################################
## IN THE '.fRwindowsLayout' frame -- DEFINE
##  2 RADIOBUTTONS, preceded by a LABEL widget.
##+########################################################

label .fRwindowsLayout.labelLAYOUT \
   -text "$aRtext(labelLAYOUT)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

## DEFINE Radiobuttons for display patterns :

radiobutton  .fRwindowsLayout.radbuttLAYOUT1x1 \
   -text "$aRtext(radbuttLAYOUT1x1)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "1x1" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRwindowsLayout.radbuttLAYOUT2x1 \
   -text "$aRtext(radbuttLAYOUT2x1)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "2x1" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRwindowsLayout.radbuttLAYOUT3x1 \
   -text "$aRtext(radbuttLAYOUT3x1)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "3x1" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRwindowsLayout.radbuttLAYOUT2x2 \
   -text "$aRtext(radbuttLAYOUT2x2)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "2x2" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


radiobutton  .fRwindowsLayout.radbuttLAYOUT3x2 \
   -text "$aRtext(radbuttLAYOUT3x2)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "3x2" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


radiobutton  .fRwindowsLayout.radbuttLAYOUT3x3 \
   -text "$aRtext(radbuttLAYOUT3x3)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "3x3" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


radiobutton  .fRwindowsLayout.radbuttLAYOUT4x3 \
   -text "$aRtext(radbuttLAYOUT4x3)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "4x3" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


## RADVARlayout is the variable for these 'layout' radiobuttons.
## Set an initial value and set the text in the 'Next' button.

#  set RADVARlayout "1x1"
#  set N2read 1

   set RADVARlayout "2x1"
   set N2read 2

#  set RADVARlayout "3x1"
#  set N2read 3

#  set RADVARlayout "2x2"
#  set N2read 4

#   set RADVARlayout "3x2"
#   set N2read 6

#  set RADVARlayout "3x3"
#  set N2read 9

#  set RADVARlayout "4x3"
#  set N2read 12

.fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) $N2read"


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

pack  .fRwindowsLayout.labelLAYOUT \
      .fRwindowsLayout.radbuttLAYOUT1x1 \
      .fRwindowsLayout.radbuttLAYOUT2x1 \
      .fRwindowsLayout.radbuttLAYOUT3x1 \
      .fRwindowsLayout.radbuttLAYOUT2x2 \
      .fRwindowsLayout.radbuttLAYOUT3x2 \
      .fRwindowsLayout.radbuttLAYOUT3x3 \
      .fRwindowsLayout.radbuttLAYOUT4x3 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 


##+########################################################
## IN THE '.fRlevels' frame -- DEFINE
## 2 RADIOBUTTONS, preceded by a LABEL widget.
##+########################################################

label .fRlevels.labelLEVELS \
   -text "$aRtext(labelLEVELS)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

## DEFINE Radiobuttons for Levels :

radiobutton  .fRlevels.radbuttLEVELSone \
   -text "$aRtext(radbuttLEVELSone)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlevels \
   -value "one" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRlevels.radbuttLEVELSall \
   -text "$aRtext(radbuttLEVELSall)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlevels \
   -value "all" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

## RADVARlevels is the var for these 2 radiobuttons.
## Set an initial value.
  set RADVARlevels "one"
# set RADVARlevels "all"

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

pack  .fRlevels.labelLEVELS \
      .fRlevels.radbuttLEVELSone \
      .fRlevels.radbuttLEVELSall \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 


##+########################################################
## IN THE 'fRsense' frame -- DEFINE
## 2 RADIOBUTTONS, preceded by a LABEL widget.
##+########################################################

label .fRsense.labelCASESENSE \
   -text "$aRtext(labelCASESENSE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

## DEFINE Radiobuttons for CaseSensitivity :

radiobutton  .fRsense.radbuttSENSE \
   -text "$aRtext(radbuttSENSE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARcasesense \
   -value "case-sensitive" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRsense.radbuttNOSENSE \
   -text "$aRtext(radbuttNOSENSE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARcasesense \
   -value "case-INsensitive" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


## RADVARcasesense is the var for these 2 radiobuttons.
## Set an initial value.
  set RADVARcasesense "case-sensitive"
# set RADVARcasesense "case-INsensitive"


## PACK the widgets in the 'fRsense' frame:

pack  .fRsense.labelCASESENSE \
      .fRsense.radbuttSENSE \
      .fRsense.radbuttNOSENSE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+############################################################
## IN THE 'fRfileSize' frame -- DEFINE
## 1 ENTRY field and 2 RADIOBUTTONS, preceded by a LABEL widget.
##+############################################################

label .fRfileSize.labelFILESIZE \
   -text "$aRtext(labelFILESIZE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

entry .fRfileSize.entryFILESIZE \
   -textvariable ENTRYfileSize \
   -bg $entryBKGD \
   -width 10 \
   -font fontTEMP_fixedwidth \
   -relief sunken \
   -bd $BDwidthPx_entry

## DEFINE Radiobuttons for File Size :

radiobutton  .fRfileSize.radbuttBIGGER \
   -text "$aRtext(radbuttBIGGER)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARfilesize \
   -value "bigger" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRfileSize.radbuttSMALLER \
   -text "$aRtext(radbuttSMALLER)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARfilesize \
   -value "smaller" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


## RADVARfilesize is the var for these 2 radiobuttons.
## Set an initial value.
  set RADVARfilesize "bigger"
# set RADVARfilesize "smaller"


## PACK the widgets in the 'fRfileSize' frame:
## (in such a way that we can experiment with
##  letting the entry field expand)

pack .fRfileSize.labelFILESIZE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfileSize.entryFILESIZE \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRfileSize.radbuttBIGGER \
     .fRfileSize.radbuttSMALLER \
   -side left \
   -anchor w \
   -fill none \
   -expand 0



##+########################################################
## IN THE 'fRfileAge' frame -- DEFINE 
## 1 ENTRY field and 2 RADIOBUTTONS, preceded by a LABEL widget.
##+########################################################

label .fRfileAge.labelFILEAGE \
   -text "$aRtext(labelFILEAGE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

entry .fRfileAge.entryFILEAGE \
   -textvariable ENTRYfileAge \
   -bg $entryBKGD \
   -width 10 \
   -font fontTEMP_fixedwidth \
   -relief sunken \
   -bd $BDwidthPx_entry


## DEFINE Radiobuttons for File Age :

radiobutton  .fRfileAge.radbuttOLDER \
   -text "$aRtext(radbuttOLDER)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARfileage \
   -value "older" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRfileAge.radbuttYOUNGER \
   -text "$aRtext(radbuttYOUNGER)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARfileage \
   -value "younger" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi


## RADVARfileage is the var for these 2 radiobuttons.
## Set an initial value.
  set RADVARfileage "older"
# set RADVARfileage "younger"


## PACK the widgets in the 'fRfileAge' frame:
## (in such a way that we can experiment with
##  letting the entry field expand)

pack .fRfileAge.labelFILEAGE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfileAge.entryFILEAGE \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRfileAge.radbuttOLDER \
     .fRfileAge.radbuttYOUNGER \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## IN THE 'fRfileType' frame -- DEFINE 
## 1 ENTRY field, with 2 LABEL widgets, before and after.
##+########################################################

label .fRfileType.labelFILETYPE \
   -text "$aRtext(labelFILETYPE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

entry .fRfileType.entryFILETYPE \
   -textvariable ENTRYfileType \
   -bg $entryBKGD \
   -width 10 \
   -font fontTEMP_fixedwidth \
   -relief sunken \
   -bd $BDwidthPx_entry

label .fRfileType.labelFILETYPE2 \
   -text "$aRtext(labelFILETYPE2)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief  flat \
   -bd $BDwidthPx_label


## PACK the widgets in the 'fRfileType' frame:
## (in such a way that we can experiment with
##  letting the entry field expand)

pack .fRfileType.labelFILETYPE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfileType.entryFILETYPE \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRfileType.labelFILETYPE2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+#####################################################################
## END OF SECTION TO DEFINE AND PACK THE GUI WIDGETS.
##+#####################################################################

##+#####################################################################
##+#####################################################################
## DEFINE BINDINGS:  button1-release bindings on some widgets ???
##                   Return-key bindings on some entry widgets ???
##+#####################################################################

# bind .fR?????.???widget???  <ButtonRelease-1>  {??proc??}
# bind .fR?????.???widget???  <Return>  {??proc??}

## As the user types in the file-mask entry field (KeyRelease events), make sure
## the area near the insertion cursor or the character entry is showing.
## ('KeyRelease' works, but 'KeyPress' leaves the insert cursor at the
## end of the field and ALWAYS slightly out of view to the right. It's as if
## a backspace occurs after the KeyPress.)

bind .fRfileMask.entryFILEMASK <KeyRelease> \
   {.fRfileMask.entryFILEMASK xview insert}

## FOR TESTING:
#   bind .fRfileMask.entryFILEMASK <KeyPress> {
#      set entFILEMASKxview [.fRfileMask.entryFILEMASK xview]
#      puts ".fRfileMask.entryFILEMASK 'offset' and 'span': $entFILEMASKxview"
#   }

## An alternative?
# bind .fRfileMask.entryFILEMASK <KeyRelease> \
#     {.fRfileMask.entryFILEMASK xview moveto 1.0}


##+############################################################
## For a button1-release on the 'player' radiobuttons, call the
## 'set_player_command' proc to set the 'VARcommand' variable.
##+############################################################

bind .fRdisplayPgm.radbuttDPGM1 <ButtonRelease-1>  {set_player_command}

bind .fRdisplayPgm.radbuttDPGM2 <ButtonRelease-1>  {set_player_command}


##+#################################################################
## For a button1-release on the 'layout' radiobuttons, set the
##'N2read' variable --- and change the number on the 'Next' button.
##    (Note that clicking on a 'layout' radiobutton sets the
##     'RADVARlayout' variable.)
##+#################################################################

bind .fRwindowsLayout.radbuttLAYOUT1x1 <ButtonRelease-1>  {
   ## RADVARlayout is "1x1"
   set N2read 1
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 1"
}

bind .fRwindowsLayout.radbuttLAYOUT2x1 <ButtonRelease-1>  {
   ## RADVARlayout is "2x1"
   set N2read 2
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 2"
}

bind .fRwindowsLayout.radbuttLAYOUT3x1 <ButtonRelease-1>  {
   ## RADVARlayout is "3x1"
   set N2read 3
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 3"
}

bind .fRwindowsLayout.radbuttLAYOUT2x2 <ButtonRelease-1>  {
   ## RADVARlayout is "2x2"
   set N2read 4
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 4"
}

bind .fRwindowsLayout.radbuttLAYOUT3x2 <ButtonRelease-1>  {
   ## RADVARlayout is "3x2"
   set N2read 6
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 6"
}

bind .fRwindowsLayout.radbuttLAYOUT3x3 <ButtonRelease-1>  {
   ## RADVARlayout is "3x3"
   set N2read 9
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 9"
}

bind .fRwindowsLayout.radbuttLAYOUT4x3 <ButtonRelease-1>  {
   ## RADVARlayout is "4x3"
   set N2read 12
   .fRbuttons.buttNEXT configure -text "$aRtext(buttonNEXT) 12"
}


##+#####################################################################
##+#####################################################################
## DEFINE PROCEDURES:
##
##  'get_filemask'             - called by the 'Browse...' button
##
##  'get_chars_before_last'    - called by proc 'get_filemask'
##
##  'check_for_criterion_change' - called by the following
##                                 'findAniGIFs_forCriteria' proc
##
##  'findAniGIFs_forCriteria'    - called by several procs below, namely
##                                 - 'count_aniGIF_files'
##                                 - 'show_aniGIF_filenames'
##                                 - 'play_nextN_aniGIF_files' 
##
##  'count_aniGIF_files'       - called by the 'CountAniGIFs' button.
##
##  'show_aniGIF_filenames'    - called by the 'ShowAniGIFfilenames' button.
##
##+-------------------------------------
## For viewing the 'found' aniGIF files:
##
##  'set_player_command'       - called by button1-release bindings on the
##                               player radiobuttons.
##
##  'start_playing_aniGIF_files' - called by the 'LaunchPlayerJob' button.
##
##  'play_nextN_aniGIF_files'    - called by the 'start_playing_aniGIF_files'
##                                 proc and the 'Next N' button.
##
##  'set_window_positions'     - called by 'play_nextN_aniGIF_files' proc
##                               to set the window positions according
##                               to the current user-selected windows-layout.
##+-------------------------------------
##
##   'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
##                                         Also used via the 'CountFilenames'
##                                         and 'ShowFilenames' buttons.
##+#####################################################################
##+#####################################################################

##+#####################################################################
## PROC:  'get_filemask'
##+#####################################################################
## PURPOSE: To get the fully-qualified name of a file and put the
##          name into global var 'ENTRYfileMask'.
##
##          The user may change the filename (after the end of the
##          directory name) to change it into a file-mask. 
##
## CALLED BY: the '-command' option of the 'Browse ...' button.
##+#####################################################################

proc get_filemask {} {

   global ENTRYfileMask env curDIR

   ## Get a file name (possibly to make a mask).

   set fName [tk_getOpenFile -parent .  \
      -title "Select Directory-and-Filename - the latter as basis for a mask" \
      -initialdir "$curDIR" ]

   ## FOR TESTING:
   #   puts "fName : $fName"

   ## Check if fName var is empty.

   if {"$fName" == ""} {return}

   ## Put $fName in ENTRYfileMask. Reset the curDIR var.

   if {[file exists "$fName"]} {
      set ENTRYfileMask "$fName"

      ## Put the end of the filename in view.
      .fRfileMask.entryFILEMASK xview end

      set curDIR [ get_chars_before_last / in "$ENTRYfileMask" ]
   }
   ## END OF if directory-exists

}
## END OF PROC  'get_filemask'


##+###################################################################
## PROC:  'get_chars_before_last' -
##+###################################################################
## INPUT:  A character and a string.
##         Note: The "in" parameter is there only for clarity.
##
## OUTPUT: Returns all of the characters in the string "strng" that
##         are BEFORE the last occurence of the characater "char".
##
## CALLED BY: proc 'get_filemask'
##+##################################################################

proc get_chars_before_last { char in strng } {

   set endIDX [ expr [string last $char $strng ] - 1 ]
   set output [ string range $strng 0 $endIDX ]

   ## FOR TESTING:
   # puts "From 'get_chars_before_last' proc:"
   # puts "STRING: $strng"
   # puts "CHAR: $char"
   # puts "RANGE up to LAST CHAR - start: 0   endIDX: $endIDX"

   return $output

}
## END OF PROC 'get_chars_before_last'


##+###################################################################
## PROC:  'get_chars_after_last'
##+###################################################################
## INPUT:  A character and a string.
##         Note: The "in" parameter is there only for clarity.
##
## OUTPUT: Returns all of the characters in the string "strng" that
##         are AFTER the last occurence of the characater "char".
##
## CALLED BY: 3 'aniGIF' procs below
##+##################################################################

proc get_chars_after_last { char in strng } {

   set lastIDX [ expr [string last $char $strng ] + 1 ]
   set output [ string range $strng $lastIDX end ]

   ## FOR TESTING:
   # puts "From 'get_chars_before_last' proc:"
   # puts "STRING: $strng"
   # puts "CHAR: $char"
   # puts "RANGE up to LAST CHAR - start: 0   lastIDX: $lastIDX"

   return $output

}
## END OF PROC 'get_chars_after_last'


##+#################################################################
## PROC  'check_for_criterion_change'
##+#################################################################
## PURPOSE: Returns 1 or 0 depending on whether the 'find' criteria
##          have been changed or not.
##
## CALLED BY: proc 'findImages_andCountPrintOrView'
##+#################################################################

proc check_for_criterion_change {} {

   ## INPUT globals:
   global ENTRYfileMask RADVARlevels RADVARcasesense \
          RADVARfilesize ENTRYfileSize \
          RADVARfileage ENTRYfileAge ENTRYfileType \
          PREV_ENTRYfileMask PREV_RADVARlevels PREV_RADVARcasesense \
          PREV_RADVARfilesize PREV_ENTRYfileSize \
          PREV_RADVARfileage PREV_ENTRYfileAge PREV_ENTRYfileType

   ## FOR TESTING:
   if {0} {
      puts "Entering proc 'check_for_criterion_change'."
      puts "PREV_ENTRYfileMask: $PREV_ENTRYfileMask       ENTRYfileMask: $ENTRYfileMask"
      puts "PREV_RADVARlevels: $PREV_RADVARlevels         RADVARlevels: $RADVARlevels"
      puts "PREV_RADVARcasesense: $PREV_RADVARcasesense   RADVARcasesense: $RADVARcasesense"
      puts "PREV_RADVARfilesize: $PREV_RADVARfilesize     RADVARfilesize: $RADVARfilesize"
      puts "PREV_ENTRYfileSize: $PREV_ENTRYfileSize       ENTRYfileSize: $ENTRYfileSize"
      puts "PREV_RADVARfileage: $PREV_RADVARfileage       RADVARfileage: $RADVARfileage"
      puts "PREV_ENTRYfileAge: $PREV_ENTRYfileAge         ENTRYfileAge: $ENTRYfileAge"
      puts "PREV_ENTRYfileType: $PREV_ENTRYfileType       ENTRYfileType: $ENTRYfileType"
   }

   ## Return 1 if at least one of the criteria has been changed.
   ## Return 0 otherwise.

   if {"$PREV_ENTRYfileMask" != "$ENTRYfileMask"} {
      return 1
   } elseif {"$PREV_RADVARlevels" != "$RADVARlevels"} {
      return 1
   } elseif {"$PREV_RADVARcasesense" != "$RADVARcasesense"} {
         return 1
   } elseif {"$PREV_RADVARfilesize" != "$RADVARfilesize"} {
      return 1
   } elseif {"$PREV_ENTRYfileSize" != "$ENTRYfileSize"} {
      return 1
   } elseif {"$PREV_RADVARfileage" != "$RADVARfileage"} {
      return 1
   } elseif {"$PREV_ENTRYfileAge" != "$ENTRYfileAge"} {
      return 1
   } elseif {"$PREV_ENTRYfileType" != "$ENTRYfileType"} {
      return 1
   } else {
      return 0
   }

}
## END OF PROC 'check_for_criterion_change'


##+#############################################################
## PROC  'findAniGIFs_forCriteria'
##+#############################################################
## PURPOSE: *IF* the criteria have changed (as determined by a
##          call to proc 'check_for_criterion_change'),
##          the shell script 'findAniGIFfiles_forCriteria.sh'
##          is run to (re)build the temporary file, $outFILE,
##          containing the 'found' aniGIF filenames.
##
##          This proc also checks that 2 entry field values in
##          vars ENTRYfileSize and ENTRYfileAge are integers,
##          if they are not blank (null).
##
##          NOTE: The shell script 'findAniGIFfiles_forCriteria.sh'
##                calls on the shell script 'file-identify_aniGIF.sh'
##                to help determine which GIF files are
##                animated GIF files.
## 
## CALLED BY: several procs below, namely
##              - 'count_aniGIF_files'
##              - 'show_aniGIF_filenames'
##              - 'play_nextN_aniGIF_files'
##+#################################################################

proc findAniGIFs_forCriteria {} {

   global DIRscripts outFILE \
      ENTRYfileMask   PREV_ENTRYfileMask \
      RADVARlevels    PREV_RADVARlevels \
      RADVARcasesense PREV_RADVARcasesense \
      RADVARfilesize  PREV_RADVARfilesize \
      ENTRYfileSize   PREV_ENTRYfileSize \
      RADVARfileage   PREV_RADVARfileage \
      ENTRYfileAge    PREV_ENTRYfileAge \
      ENTRYfileType   PREV_ENTRYfileType

   ##################################################
   ## If the 'find' criteria have not changed, simply
   ## exit and use the existing $outFILE.
   ##################################################

   if {[check_for_criterion_change] != 1} {return}

   ####################################################
   ## If we get here, the 'find' criteria have changed.
   ## Proceed to (re)build $outFILE.
   ####################################################

   #######################################################
   ## Remove trailing and leading blanks (if any) from the
   ## user entries in the 'entry' widgets.
   #######################################################

   set ENTRYfileMask [string trim $ENTRYfileMask]
   set ENTRYfileSize [string trim $ENTRYfileSize]
   set ENTRYfileAge  [string trim $ENTRYfileAge]
   set ENTRYfileType [string trim $ENTRYfileType]

   #################################################################
   ## The variables RADVARdisplaypgm, RADVARlevels, RADVARcasesense,
   ## RADVARfilesize, and RADVARfileage are set as single
   ## words in this script and should not need any editing
   ## or checking (if bug-free).
   #################################################################

   ##########################################################
   ## Check that ENTRYfileSize and ENTRYfileAge are integers,
   ## if they are not blank (null).
   ##########################################################

   if {"$ENTRYfileSize" != ""} {
      if {![string is integer -strict "$ENTRYfileSize"]} {
         popup_msgVarWithScroll .topErr \
            "File Size: $ENTRYfileSize  is NOT NUMERIC."
         return
      }
   }
   

   if {"$ENTRYfileAge" != ""} {
      if {![string is integer -strict "$ENTRYfileAge"]} {
         popup_msgVarWithScroll .topErr \
            "File Age: $ENTRYfileAge  is NOT NUMERIC."
         return
      }
   }


   ## FOR TESTING:  (to dummy out the rest of this proc)

   # return

   #############################################################
   ## Reset the PREV variables since there has been at least one
   ## criterion change.
   #############################################################

   set PREV_ENTRYfileMask   "$ENTRYfileMask"
   set PREV_RADVARlevels    "$RADVARlevels"
   set PREV_RADVARcasesense "$RADVARcasesense"
   set PREV_RADVARfilesize  "$RADVARfilesize"
   set PREV_ENTRYfileSize   "$ENTRYfileSize"
   set PREV_RADVARfileage   "$RADVARfileage"
   set PREV_ENTRYfileAge    "$ENTRYfileAge"
   set PREV_ENTRYfileType   "$ENTRYfileType"

   ############################################################
   ## Make sure we put the output in a new (empty) output file.
   ############################################################

   catch {exec rm "$outFILE"}


   #############################################################
   ## Run the 'find' script in an 'xterm' so that 'progress
   ## indicators' can be seen. Do not use '-hold' on 'xterm'
   ## so that the user does not have to close the 'xterm' window.
   ## Run the xterm-and-find-script in the foreground --- to
   ## prevent another 'find' from being started.
   #############################################################
   ## FOR TESTING: You can use the '-hold' parameter of 'xterm'
   ## to hold the xterm window open, to examine error messages.
   ## Good place to put it: After '-bg black'.
   #############################################################

   set RETcode [catch {exec xterm -fg white -bg black \
      -title "To 'kill' the FIND of image files, close this window." \
      -geometry 90x5-30-30 -e \
      $DIRscripts/findAniGIFfiles_forCriteria.sh "$ENTRYfileMask" \
      "$RADVARlevels" "$RADVARcasesense" \
      "$RADVARfilesize" "$ENTRYfileSize" \
      "$RADVARfileage"  "$ENTRYfileAge" \
      "$ENTRYfileType"  "$outFILE"}  FindPID]

   ## FOR TESTING:
   #   puts "FindPID: $FindPID"

   if { $RETcode != 0 } then {
      set ERRmsg \
      "ERROR from attempt to run the 'findAniGIFfiles_forCriteria.sh' script.

RETcode: $RETcode
Message: $FindPID"
         popup_msgVarWithScroll .topErr "$ERRmsg"
         return
   }
   ## END OF   if { $RETcode != 0 }


}
## END OF PROC 'findAniGIFs_forCriteria'


##+#############################################################
## PROC:  'count_aniGIF_files'
##+#############################################################
## PURPOSE: Puts the aniGIF files for the current criteria
##          in $outFILE if the criteria have changed,
##          and then counts the filenames in $outFILE.          
##
## CALLED BY: the 'CountAniGIFs'  button
##+#############################################################

proc count_aniGIF_files {} {

   ## STATEMENT TO DUMMY OUT THIS PROC (during development)
   #   return

   global outFILE ENTRYfileMask

   #####################################################
   ## If the criteria have changed, do the 'find' to
   ## (re)build $outFILE.
   ####################################################

   findAniGIFs_forCriteria

   ####################################################
   ## Apply 'wc -l' to $outFILE, to get the file count.
   ####################################################

   set wcOUT [exec wc -l "$outFILE"]
   set VARlist [ split "$wcOUT" " " ]
   set FILEScount [lindex $VARlist 0]

   ####################################################
   ## Set the count message and display it in a popup
   ## window.
   ####################################################

   set COUNTmsg "$FILEScount aniGIF files were 'found' USING the
current file mask:
   $ENTRYfileMask

and other GUI settings."

   popup_msgVarWithScroll .topCount "$COUNTmsg"

}
## END OF proc 'count_aniGIF_files'


##+#############################################################
## PROC:  'show_aniGIF_filenames'
##+#############################################################
## PURPOSE: Puts the aniGIF files for the current criteria
##          in $outFILE if the criteria have changed,
##          and then shows the filenames in $outFILE. 
##
## CALLED BY: the 'ShowAniGIFfilenames'  button
##+#############################################################

proc show_aniGIF_filenames {} {

   ## STATEMENT TO DUMMY OUT THIS PROC (during development)
   #  return

   global outFILE ENTRYfileMask

   #####################################################
   ## If the criteria have changed, do the 'find' to
   ## (re)build $outFILE.
   ####################################################

   findAniGIFs_forCriteria

   ####################################################
   ## Apply 'cat' to $outFILE, to get the file names.
   ####################################################

   set FILESlist [exec cat "$outFILE"]

   ######################################################
   ## Set the filenames message and display it in a popup
   ## window.
   ######################################################

   if {"$FILESlist" == "" } {set FILESlist "NONE"}

   set FILESmsg "Filenames of the aniGIF files that were 'found' USING the
current file mask:
   $ENTRYfileMask

and other GUI settings:

$FILESlist"

   popup_msgVarWithScroll .topList "$FILESmsg"

}
## END OF proc 'show_aniGIF_filenames'


##+#############################################################
## PROC:  'set_player_command'
##+#############################################################
## PURPOSE: For a user-selected aniGIF player, this proc sets
##          the variable 'VARcommand', which holds the name
##          of the player program and some of its command-line
##          parameters.
##
## CALLED BY: button1-release on a player radiobutton
##+#############################################################

proc set_player_command {} {

   global RADVARdisplaypgm VARcommand

   ###################################################################
   ## SET 'VARcommand' with the 'animate' command and
   ## some of its command-line options (if any).
   ###################################################################

   if {"$RADVARdisplaypgm" == "1"} {
      # set VARcommand "/usr/bin/animate "
      set VARcommand "animate "
      return
   }

   ###################################################################
   ## SET 'VARcommand' with the 'gifview' command and
   ## some of its command-line options (like '-a).
   ###################################################################

   if {"$RADVARdisplaypgm" == "2"} {
      # set VARcommand "/usr/bin/gifview -a "
      set VARcommand "gifview -a "
      return
   }

   popup_msgVarWithScroll .topList \
      "Invalid value of variable RADVARdisplaypgm in proc
'set_player_command'. Value: $RADVARdisplaypgm"

}
## END OF proc 'set_player_command'


##+#############################################################
## PROC:  'start_playing_aniGIF_files'
##+#############################################################
## PURPOSE: Starts playing the aniGIF files of $outFILE by, first,
##          opening $outFILE.
##
##          Then uses the 'play_nextN_aniGIF_files' proc to
##          read the first $N2read files from $outFILE
##          and play them with the currently selected player.
##
## CALLED BY: the 'LaunchPlayerJob'  button
##+#############################################################

proc start_playing_aniGIF_files {} {

   ## STATEMENT TO DUMMY OUT THIS PROC (during development)
   #   return

   global STOPvar0or1 aRtext outFILE  ENTRYfileMask \
      fileID NaniGIFfiles
   # global thisDIR

   #####################################################
   ## Reset the 'stop' variable in case it was set to 1
   ## to stop a previous display sequence.
   #####################################################

   set STOPvar0or1 0
   .fRbuttons.buttSTOP configure -text "$aRtext(buttonSTOP1)"


   ######################################################
   ## Check that there is at least one file to display.
   ######################################################

   set wcOUT [exec wc -l "$outFILE"]
   set VARlist [ split "$wcOUT" " " ]
   set FILEScount [lindex $VARlist 0]

   if {$FILEScount < 1 } {
      set ERRmsg "No image filenames were 'found' USING the
current file mask:
   $ENTRYfileMask

and other GUI settings."
         popup_msgVarWithScroll .topList "$ERRmsg"
         return
   }
   ## END OF if {$FILEScount < 1 }

   ###############################################################
   ## Prepare for playing the aniGIF files in $outFILE ...
   ## mainly, open the file.
   ###############################################################

   set fileID [open "$outFILE"]

   ##################################################
   ## Initialize a counter for keeping a count of the
   ## number of aniGIF files read so far.
   ##################################################

   set NaniGIFfiles 0

   #######################################################
   ## Enable the 'Next' & 'Stop' buttons --- and disable the
   ## 'Launch' button (until the user is done playing
   ## 'found' files and $outFILE file is closed).
   #######################################################

   .fRbuttons.buttNEXT   configure -state normal
   .fRbuttons.buttSTOP   configure -state normal
   .fRbuttons.buttLAUNCH configure -state disabled

   #################################################################
   ## Call the 'play_nextN_aniGIF_files' proc to
   ## read the first $N2read files from $outFILE
   ## and play them with the currently selected player.
   #################################################################

   play_nextN_aniGIF_files

}
## END OF PROC 'start_playing_aniGIF_files'


##+#############################################################
## PROC:  'play_nextN_aniGIF_files'
##+#############################################################
## PURPOSE: For a given value of global variable 'N2read' (set
##          via bindings on the 'layout' radiobuttons), this proc
##          reads the next $N2read files from the previously
##          opened $outFILE, which contains fully-qualified
##          filenames.
##
##          This proc uses the display program (and suitable 
##          parameters) in the variable 'VARcommand' to show
##          the $N2read files.
##
##          This proc uses 2 array variables, aRxpositions and
##          aRypositions, to set '-geometry +x+y' player-command 
##          options to locate the $N2read player-windows on the screen.
##          (We use '-geometry' for the 'animate' player, and '--geometry'
##           for the 'gifview' player.)
##
##           (The aRxpositions and aRypositions arrays are set in
##            a call to proc 'set_window_positions', which uses
##            the 'RADVARlayout' variable (and screensize) to
##            determine the x,y positions for the player windows.)
##
##          If the end-of-file is encountered, this proc
##          closes $outFILE before exiting.
##
##          The global variable 'fileID' --- which was set in
##          proc 'start_playing_aniGIF_files' --- is used to do the
##          the file reads -- and is used to close the file when
##          end-of-file is reached (or the STOP variable has been set).
##
##          This proc increments the variable 'NaniGIFfiles'
##          to keep track of the total number of files read.
##
## NOTE1: There are 3 conditions under which the $outFILE
##        reading-and-playing (done only by this 'play' proc) stops:
##          1) If the STOP button has been pressed (STOP var set to 1).
##          2) We encounter end-of-file in reading the $outFILE
##             file with this proc.
##          3) We have read $N2read files with this proc.
##         We want to close the $outFILE file in cases 1 and 2,
##         but leave the file open in case 3.
##
## NOTE2: After this proc has displayed (at most) $N2read aniGIF files,
##        this GUI falls into an GUI-event-handling state --- until
##        the user clicks the 'Next N' button (again). While the
##        GUI is in the event-handling state, the user can click on
##        the GUI to change radiobutton settings or click on 
##        buttons like the 'Help' button.
##             
## CALLED BY: by the 'start_playing_aniGIF_files' proc and by
##            the 'Next N' button
##+#############################################################

proc play_nextN_aniGIF_files {} {

   ## DUMMY OUT THIS PROC (during development)
   #   return

   global STOPvar0or1 fileID N2read aRxpositions aRypositions \
      VARcommand RADVARdisplaypgm aRtext NaniGIFfiles outFILE
   # global thisDIR
   # global RADVARlayout

   ## FOR TESTING:
   #  puts "Started proc 'play_nextN_aniGIF_files'."

   ############################################################
   ## CHECK TO SEE IF THE DISPLAY SEQUENCE SHOULD STOP.  I.e.,
   ## if STOPvar0or1 is set to 1, the 'Next N' button is to be
   ## de-activated --- until the 'Launch' button is used again.
   ## Make sure that 'fileID' is closed.
   ############################################################

   if { $STOPvar0or1 == 1 } {
      catch {close $fileID} catchRESULT

      ## FOR TESTING:
      if {0} {
         puts "PROC 'play_nextN_aniGIF_files' close 'found-files' file:"
         puts "fileID: $fileID"
         puts "catchRESULT: $catchRESULT"
      }

      popup_msgVarWithScroll .topCount \
         "The 'STOP' button was used to call for a stop
in the playing of the 'found' files.

If you are ready to start again, click on
the '$aRtext(buttonLAUNCH)' button."

      #########################################################
      ## Reset the 'stop' variable to zero (off), and
      ## reset the 'stop' button text, to indicate the 'stop'
      ## button is ready to handle another display sequence.
      ########################################################

      set STOPvar0or1 0
      .fRbuttons.buttSTOP configure -text "$aRtext(buttonSTOP1)"

      ##########################################################
      ## Disable the 'Next' & 'Stop' buttons. Enable the 'Launch'
      ## button (to allow for starting another 'find' job ---
      ## to play a $outFILE file, again).
      ##########################################################

      .fRbuttons.buttNEXT   configure -state disabled
      .fRbuttons.buttSTOP   configure -state disabled
      .fRbuttons.buttLAUNCH configure -state normal

      return
   }
   ## END OF  'if { $STOPvar0or1 == 1 }'


   ###############################################################
   ## At this point, STOP has NOT been requested, so we continue
   ## on to display the next $N2read aniGIF files.
   ###############################################################

   ###############################################################
   ## For the current windows-layout, in variable 'RADVARlayout',
   ## set the positions of the $N2read windows --- in
   ## variables 'aRxpositions' and 'aRypositions'.
   ##
   ## NOTE: We allow the 'layout' to be changed after the $outFILE
   ##       is opened --- i.e. after the user clicks the
   ##       'Launch' (begin playing) button.
   ###############################################################

   set_window_positions


   ###################################################################
   ## START OF A WHILE-LOOP for the 'gets' file-READING of $outFILE.
   ## This loop reads NO MORE THAN $N2read
   ## aniGIF files from the $outFILE file --- and starts playing
   ## each aniGIF in a player window, which is located on the screen
   ## according to the 'aRxpositions' & 'aRypositions' array variables.
   ##
   ## We use the 'READcnt' variable to check when we have read
   ## 'N2read' aniGIF files.
   ##
   ## The while-test below is equivalent to 'while {![eof $fileID]}'.
   ###################################################################

   set READcnt 0

   while {[eof $fileID] == 0} {

      ############################################################
      ## GET the next line (up to a line feed).
      ## (We could get the line-length in the same statement.)
      ############################################################

      # set lineLen [gets $fileID line]

      gets $fileID line

      ## FOR TESTING:
      #  puts "line: $line"


      ###################################################################
      ## Strip any trailing or leading spaces from the filename line.
      ###################################################################

      set aniGIFfilename "[string trim $line]"

      #################################################################
      ## If the line is empty, go read the next line.
      #################################################################

      if { "$aniGIFfilename" == "" } {continue}

      ##################################################################
      ## Check if the filename exists --- just in case the user or some
      ## job on the system has removed/renamed the file. If it does not
      ## exist, skip this line and read the next line.
      ##################################################################

      ## FOR TESTING:
      #   puts "aniGIFfilename: $aniGIFfilename"

      if {![eval file exists "$aniGIFfilename"]} {continue}

      #########################################################################
      ## Increment READcnt and NaniGIFfiles.
      ## We use READcnt to help determine how many aniGIFs to show at one time.
      ## We use NaniGIFfiles to display the number of aniGIF's shown so far.
      #########################################################################

      incr READcnt
      incr NaniGIFfiles


      ###################################################################
      ## At this point the filename line appears to be OK, so we
      ## PLAY THE AniGIF FILE.
      ###################################################################

      #####################################################################
      ## First, we set the 'geometry' parm for placing the player window
      ## for this particular aniGIF, among the (up to) $N2read aniGIF files
      ## that are to be displayed by the call to this proc.
      ##
      ## We use '-geometry' for the 'animate' player (RADVARdisplaypgm=1)
      ## and '--geometry' for the 'gifview' player (RADVARdisplaypgm=2).
      ####################################################################

      set OPTgeom "--geometry"
      if {$RADVARdisplaypgm == 1} {set OPTgeom "-geometry"}

      set PARMgeom "$OPTgeom +$aRxpositions($READcnt)+$aRypositions($READcnt)"


      ####################################################################
      ## SOME SYNTAX NOTES on running the player program via a Tcl 'exec':
      ####################################################################
      ## On page 105 of the 4th edition of 'Practical Programming in Tcl & Tk',
      ## is the following quote on the Tcl 'exec' command:
      ##
      ## "The 'exec' command runs programs from your Tcl script. For example:
      ##      set d [exec date]
      ## The standard output of the program is returned as the value of
      ## the 'exec' command. However, if the program writes to its standard
      ## error channel or exits with a nonzero status code, then 'exec'
      ## raises an error. If you do not care about the exit status, or you
      ## use a program that insists on writing to standard error, then you
      ## can use 'catch' to mask the errors:
      ##   catch {exec program arg arg} result"
      ##
      ## Unfortunately, running a player program in 'foreground' mode
      ## like this makes the button widgets on the GUI unavailable ---
      ## in particular, the 'Stop' button ... and the 'Help' button.
      ## Also, we cannot continue on in this loop --- to play up to
      ## $N2read aniGIF files, at the same time.
      ## 
      #######################################################################
      ## On page 107 of the 4th edition of 'Practical Programming in Tcl & Tk',
      ## is the following quote on the Tcl 'exec' command and 'background' mode:
      ##
      ## "A trailing '&' causes the program to run in the background.
      ##  In this case, the process identifier is returned by the 'exec'
      ##  command. Otherwise, the 'exec' command blocks during execution
      ##  of the program, and the standard output of the program is the
      ##  return code of 'exec'."
      ##
      ## Page 83 of the same book says:
      ## "'catch' returns zero if there was no error caught,
      ##   or a nonzero error code if it did catch an error."
      ####################################################################
      ## A couple of examples of using a PID (process ID) with Tcl:
      ##
      ## catch {eval exec $feREADER_text \"$FULFILname\"  &} ViewerPID
      ##
      ## set RETcode [ catch {eval exec ${feDIR}/tkGUIs/shofil.tk \
      ##    "$FULFILname" &} ViewerPID ]
      ##
      ####################################################################
      ## An alternative form of trying 'exec':
      ##    exec  /bin/sh -c "$...."
      ####################################################################

      ## A 'foreground' run per page 105.
      ## NOT USED, because it 'locks up' the GUI.

      # catch {eval exec $VARcommand $PARMgeom "$aniGIFfilename"} CatchMsg


      ###############################################################
      ## A 'background' run per page 107.
      ## (Better for implementing the 'Stop' feature via a button
      ##  on this GUI.)
      ## We use 'eval' to avoid a 'not found' error when we append
      ## some parms (or a space) to the player command.
      ###############################################################

      set RETcode [ catch {eval exec $VARcommand $PARMgeom "$aniGIFfilename" &} ViewerPID ]

      if {$RETcode != 0} {
         set ERRtext \
"Proc 'play_nextN_aniGIF_files' encountered an error on trying to show file
$EMPTYLINEcheck
in directory
$anigifDIR
using command '$VARcommand' with parameters
$PARMgeom.

RETcode: $RETcode
Message: $ViewerPID
Stopping processing."
         popup_msgVarWithScroll .topErr "$ERRtext"
         .fRbuttons.buttLAUNCH configure -state normal
         return
      }


      ## FOR TESTING:
      if {0} {
          puts ""
          puts "***********************"
          puts "VARcommand: $VARcommand"
          puts "PARMgeom:   $PARMgeom"
          puts "aniGIF filename: $aniGIFfilename"
          puts "ViewerPID: $ViewerPID"
          puts "***********************"
          puts ""
      }

      ############################################################
      ## If $READcnt is equal to (or greater than) $N2read,
      ## break out of this aniGIF-file-reading 'while' loop,
      ## and leave this proc.
      ##
      ## We use 'return' rather than 'break'. 'break' would
      ## drop us out of the 'while' loop, after which we
      ## 'close' the $outFILE file, under the assumption
      ## that we have hit end-of file.
      ##
      ## Note that after the 'return', this GUI goes into an
      ## event-handling state in which it responds to clicks
      ## on the buttons and radiobuttons of the GUI.
      ############################################################

      if { $READcnt >= $N2read } {return}

   }
   ## END OF LOOP 'while {[eof $fileID] == 0}'

   #######################################################
   ## Close the $outFILE file when the while loop is done.
   #######################################################

   catch {close $fileID} catchRESULT

   ## FOR TESTING:
   if {0} {
      puts "PROC 'play_nextN_aniGIF_files' close 'found-files' file:"
      puts "fileID: $fileID"
      puts "catchRESULT: $catchRESULT"
   }

   ##########################################################
   ## Disable the 'Next' & 'Stop' buttons. Enable the 'Launch'
   ## button (to allow for starting another 'find' job ---
   ## to start playing aniGIF's in a $outFILE file, again).
   ##########################################################

   .fRbuttons.buttNEXT   configure -state disabled
   .fRbuttons.buttSTOP   configure -state disabled
   .fRbuttons.buttLAUNCH configure -state normal


   ###############################################################
   ## Inform the user that end-of-file has been reached, and
   ## DISPLAY THE COUNT OF AniGIF-FILES-PLAYED in a small popup Tk window.
   ##   (If the user finds this annoying, this could be
   ##    deactivated by setting 1 to 0 in the 'if' statement.
   ##    Or we could show this only if NaniGIFfiles were greater than
   ##    25, say. That is, replace '1' by  '$NaniGIFfiles > 25'.)
   ###############################################################

   if {1} {
      popup_msgVarWithScroll .topCount \
         "End-of-file was reached on the 'found-filenames' file ---
$outFILE

$NaniGIFfiles aniGIF files were shown.
"
   }
   ## END OF 'if {1}'

}
## END OF proc 'play_nextN_aniGIF_files'


##+#################################################################
## PROC:  'set_window_positions'
##+#################################################################
## PURPOSE: For a given value of global variable 'RADVARlayout'
##          (and for the screensize of the user's monitor),
##          this proc calculates and puts $N2read x,y positions
##          in an array 'aRpositions'.
##
## CALLED BY: proc 'play_nextN_aniGIF_files'
##+#################################################################

proc set_window_positions {} {

   global RADVARlayout SCREENwidthPx SCREENheightPx \
      aRxpositions aRypositions
   # global N2read

   #########################################################
   ## According to RADVARlayout, load the 2 arrays:
   ## aRxpositions, aRypositions.
   #########################################################

   set XleftPx 15
   set YtopPx 30

   if {"$RADVARlayout" == "1x1"} {
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
   }

   if {"$RADVARlayout" == "2x1"} {
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
      set aRxpositions(2) [expr {$XleftPx + int(0.5 * $SCREENwidthPx)}]
      set aRypositions(2) $YtopPx
   }

   if {"$RADVARlayout" == "3x1"} {
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
      set aRxpositions(2) [expr {$XleftPx + int(0.33 * $SCREENwidthPx)}]
      set aRypositions(2) $YtopPx
      set aRxpositions(3) [expr {$XleftPx + int(0.66 * $SCREENwidthPx)}]
      set aRypositions(3) $YtopPx
   }

   if {"$RADVARlayout" == "2x2"} {
      ## ROW 1
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
      set aRxpositions(2) [expr {$XleftPx + int(0.5 * $SCREENwidthPx)}]
      set aRypositions(2) $YtopPx
      ## ROW 2
      set aRxpositions(3) $XleftPx
      set aRypositions(3) [expr {$YtopPx  + int(0.5 * $SCREENheightPx)}]
      set aRxpositions(4) [expr {$XleftPx + int(0.5 * $SCREENwidthPx)}]
      set aRypositions(4) [expr {$YtopPx  + int(0.5 * $SCREENheightPx)}]
   }

   if {"$RADVARlayout" == "3x2"} {
      ## ROW 1
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
      set aRxpositions(2) [expr {$XleftPx + int(0.33 * $SCREENwidthPx)}]
      set aRypositions(2) $YtopPx
      set aRxpositions(3) [expr {$XleftPx + int(0.66 * $SCREENwidthPx)}]
      set aRypositions(3) $YtopPx
      ## ROW 2
      set aRxpositions(4) $XleftPx
      set aRypositions(4) [expr {$YtopPx  + int(0.5 * $SCREENheightPx)}]
      set aRxpositions(5) [expr {$XleftPx + int(0.33 * $SCREENwidthPx)}]
      set aRypositions(5) [expr {$YtopPx  + int(0.5 * $SCREENheightPx)}]
      set aRxpositions(6) [expr {$XleftPx + int(0.66 * $SCREENwidthPx)}]
      set aRypositions(6) [expr {$YtopPx  + int(0.5 * $SCREENheightPx)}]
   }

   if {"$RADVARlayout" == "3x3"} {
      ## ROW 1
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
      set aRxpositions(2) [expr {$XleftPx + int(0.33 * $SCREENwidthPx)}]
      set aRypositions(2) $YtopPx
      set aRxpositions(3) [expr {$XleftPx + int(0.66 * $SCREENwidthPx)}]
      set aRypositions(3) $YtopPx
      ## ROW 2
      set aRxpositions(4) $XleftPx
      set aRypositions(4) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      set aRxpositions(5) [expr {$XleftPx + int(0.33 * $SCREENwidthPx)}]
      set aRypositions(5) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      set aRxpositions(6) [expr {$XleftPx + int(0.66 * $SCREENwidthPx)}]
      set aRypositions(6) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      ## ROW 3
      set aRxpositions(7) $XleftPx
      set aRypositions(7) [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
      set aRxpositions(8) [expr {$XleftPx + int(0.33 * $SCREENwidthPx)}]
      set aRypositions(8) [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
      set aRxpositions(9) [expr {$XleftPx + int(0.66 * $SCREENwidthPx)}]
      set aRypositions(9) [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
   }

   if {"$RADVARlayout" == "4x3"} {
      ## ROW 1
      set aRxpositions(1) $XleftPx
      set aRypositions(1) $YtopPx
      set aRxpositions(2) [expr {$XleftPx + int(0.25 * $SCREENwidthPx)}]
      set aRypositions(2) $YtopPx
      set aRxpositions(3) [expr {$XleftPx + int(0.50 * $SCREENwidthPx)}]
      set aRypositions(3) $YtopPx
      set aRxpositions(4) [expr {$XleftPx + int(0.75 * $SCREENwidthPx)}]
      set aRypositions(4) $YtopPx
      ## ROW 2
      set aRxpositions(5) $XleftPx
      set aRypositions(5) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      set aRxpositions(6) [expr {$XleftPx + int(0.25 * $SCREENwidthPx)}]
      set aRypositions(6) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      set aRxpositions(7) [expr {$XleftPx + int(0.50 * $SCREENwidthPx)}]
      set aRypositions(7) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      set aRxpositions(8) [expr {$XleftPx + int(0.75 * $SCREENwidthPx)}]
      set aRypositions(8) [expr {$YtopPx  + int(0.33 * $SCREENheightPx)}]
      ## ROW 3
      set aRxpositions(9)  $XleftPx
      set aRypositions(9)  [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
      set aRxpositions(10) [expr {$XleftPx + int(0.25 * $SCREENwidthPx)}]
      set aRypositions(10) [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
      set aRxpositions(11) [expr {$XleftPx + int(0.50 * $SCREENwidthPx)}]
      set aRypositions(11) [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
      set aRxpositions(12) [expr {$XleftPx + int(0.75 * $SCREENwidthPx)}]
      set aRypositions(12) [expr {$YtopPx  + int(0.66 * $SCREENheightPx)}]
   }


}
## END OF proc 'set_window_positions'


##+########################################################################
## PROC 'popup_msgVarWithScroll'
##+########################################################################
## PURPOSE: Report help or error conditions to the user.
##
##       We do not use focus,grab,tkwait in this proc,
##       because we use it to show help when the GUI is idle,
##       and we may want the user to be able to keep the Help
##       window open while doing some other things with the GUI
##       such as putting a filename in the filename entry field
##       or clicking on a radiobutton.
##
##       For a similar proc with focus-grab-tkwait added,
##       see the proc 'popup_msgVarWithScroll_wait' in a
##       3DterrainGeneratorExaminer Tk script.
##
## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk',
##            4th edition, by Welch, Jones, Hobbs.
##
## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg)
##            and a variable holding text (many lines, if needed).
##
## CALLED BY: 'help' button
##+########################################################################
## 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 -- like on 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_msgVarWithScroll { toplevName VARtext } {

   ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global.
   ## global env

   # bell
   # bell
  
   #################################################
   ## 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"


   #################################################
   ## 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

   ## For testing:
   #    puts "VARwidth: $VARwidth"


   ###############################################################
   ## NOTE: VARwidth works for a 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.
   ###############################################################


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

   catch {destroy $toplevName}
   toplevel  $toplevName

   # wm geometry $toplevName 600x400+100+50

   wm geometry $toplevName +100+50

   wm title     $toplevName "Note"
   # wm title   $toplevName "Note to $env(USER)"

   wm iconname  $toplevName "Note"


   #####################################
   ## In the frame '$toplevName' -
   ## DEFINE THE TEXT WIDGET and
   ## its two scrollbars --- and
   ## DEFINE an OK BUTTON widget.
   #####################################

   if {$VARheight > 10 || $VARwidth > 100} {
      text $toplevName.text \
         -wrap none \
         -font fontTEMP_fixedwidth \
         -width  $VARwidth \
         -height $VARheight \
         -bg "#f0f0f0" \
         -relief raised \
         -bd 2 \
         -yscrollcommand "$toplevName.scrolly set" \
         -xscrollcommand "$toplevName.scrollx set"

      scrollbar $toplevName.scrolly \
         -orient vertical \
         -command "$toplevName.text yview"

      scrollbar $toplevName.scrollx \
         -orient horizontal \
         -command "$toplevName.text xview"
   } else {
      text $toplevName.text \
         -wrap none \
         -font fontTEMP_fixedwidth \
         -width  $VARwidth \
         -height $VARheight \
         -bg "#f0f0f0" \
         -relief raised \
         -bd 2 
   }

   button $toplevName.butt \
      -text "OK" \
      -font fontTEMP_varwidth \
      -command  "destroy $toplevName"

   ###############################################
   ## PACK *ALL* the widgets in frame '$toplevName'.
   ###############################################

   ## Pack the bottom button BEFORE the
   ## bottom x-scrollbar widget,

   pack  $toplevName.butt \
      -side bottom \
      -anchor center \
      -fill none \
      -expand 0


   if {$VARheight > 10 || $VARwidth > 100} {
      ## Pack the scrollbars BEFORE the text widget,
      ## so that the text does not monopolize the space.

      pack $toplevName.scrolly \
         -side right \
         -anchor center \
         -fill y \
         -expand 0

      ## DO NOT USE '-expand 1' HERE on the Y-scrollbar.
      ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS
      ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA.
                
      pack $toplevName.scrollx \
         -side bottom \
         -anchor center \
         -fill x  \
         -expand 0

      ## DO NOT USE '-expand 1' HERE on the X-scrollbar.
      ## THAT KEEPS THE TEXT AREA FROM EXPANDING.

      pack $toplevName.text \
         -side top \
         -anchor center \
         -fill both \
         -expand 1
   } else {
      pack $toplevName.text \
         -side top \
         -anchor center \
         -fill both \
         -expand 1
   }


   #####################################
   ## LOAD MSG INTO TEXT WIDGET.
   #####################################

   ##  $toplevName.text delete 1.0 end
 
   $toplevName.text insert end $VARtext
   
   $toplevName.text configure -state disabled
  
}
## END OF PROC 'popup_msgVarWithScroll'


##+########################
## END of PROC definitions.
##+########################
## Set HELPtext var.
##+########################


set HELPtext "\
\ \ ** HELP for this 'Animated-GIF-file BATCH find-and-play' Utility **

** essentially a 'wrapper' for the 'find' command and an 'aniGIF' player **

This utility provides a GUI for

1) finding a 'batch' of animated-GIF files according to
   user-specified criteria that are prompted for via
   entry widgets and radiobutton widgets on the GUI,

AND

2) starting up an animated-GIF file 'player' program to 'play' the 'batch'
   of animated-GIF files that were found, via the 'find' command.
   Examples of the player program: the ImageMagick 'animate' program or
   the 'gifview' program that comes with the 'gifsicle' utility program.

Hence, the GUI is a 'front-end' for the 'find' command and
an animated-GIF-file 'player' command.

The GUI is intended to make the user aware of the main
(most useful) options available without the user needing
to reference 'man find' and 'man animate' or 'man gifview'
commands --- nor other 'find' and aniGIF-player documentation,
for example, via web searches.

The intent of this utility is to allow the user to easily
specify animated-GIF files to be played --- via specifying:
 1  - a directory-and-filename (the latter is typically a 'mask')
 2  - whether files in sub-directories should be mask-searched
 3  - whether the mask-search should be case-INsensitive
 4  - an optional file-size limitation
 5  - an optional file-age limitation.
 6  - an optional file-type string (typically 'GIF' or '89a').

In addition, the GUI prompts for
   - which aniGIF-player program to use
and
   - what pattern to use (2x1, 2x2, 3x2, or other) to display
     groups of the 'found' files.
     (The pattern determines how-many-at-a-time to display).

NOTE that most animated-GIF-file viewers, like 'animate' and 'gifview',
are oriented toward playing a single animated-GIF file --- or
playing files selected from a single 'current' directory.
In contrast ...

This utility is oriented toward AUTO-SELECTING 'aniGIF' files
(based on criteria such as a file-mask, file-size, and/or file-age)
from an ENTIRE HIERARCHY OF SUB-DIRECTORIES --- as well as
allowing mask-search in a SINGLE directory.

This utility SORTS the selected files ACCORDING TO their 
filenames and then starts playing the files. 

NOTE that a single aniGIF file can be played by selecting
a single filename and NOT changing the name to a mask ---
such as  *.gif   OR   *_ani.gif. 


****************************************
PLAYING THE ANIMATED GIF FILES IN GROUPS:
****************************************

Note that this utility is designed with the option to show
multiple animated-GIF files at a time --- in 'groups'.

This is done mainly because animated GIF files tend to be
small enough that multiple files could be played on screen
at the same time.

This 'group play' can be handy when many, many files are
found via the 'find' criteria and/or when the user wants
to review the files relatively quickly.

When the user decides to show the next N animated-GIF files,
this utility provides a 'Next N' button by which the user can
request that the next N files be displayed.

The user has the option to close the currently-open display
window(s) before starting the next group. Thus the user can
leave some animations running when starting another 'group'.

The user can 'pull' windows around to display the animations
in whatever 'layout' that one desires.

(Note that most animated-GIF files keep animating until stopped
by the user. So, if a lot of files are to be displayed, the
user will probably want to close one group of windows before
starting another group. However, the user can opt to keep a few
displays running at chosen spots on the screen while continuing
to show more files.)

********************
STOPPING THE PLAYING:
********************

If a large number of animated-GIF files are found via the 'find'
criteria, the user may find it desirable to 'bail out' of the playing
of all the 'found files'.

This can be done by clicking on the 'Stop' button of the GUI.

The currently playing animated-GIF files will continue to be
displayed, but the next time the user clicks the 'Next N' button,
a popup message will notify the user that the display of
any remaining 'found' files has been terminated.

At that point, the user can enter other 'find' criteria and
launch another display-of-found-files job.


***************
THE GUI WIDGETS:
***************

The GUI offers a generous set of hints, help, and options
for the user.  In fact, the GUI consists of about

   -  8 button widgets
   -  9 label  widgets
   -  4 entry  widgets
   - 17 radiobutton widgets in 6 groups
   -  0 checkbutton widgets
   -  0 scale widgets
   -  0 listbox widgets

The main controls are via the 'button', 'entry', and 'radiobutton'
widgets. Hence there are about 29 control options available.


**************
TYPICAL USAGE:
*************

You can use the 'Browse...' button to retrieve a full filename
to the file/mask name entry field. This allows one to establish
a 'base' directory for the 'find' command. And the user can change
the filename after the directory name, to a mask. Examples:
*.gif    OR    *_ani.gif   OR   *320x240*_ani.gif  OR  * 

Before clicking on the 'LaunchPlayerJob' button,
you can also change radiobutton settings as needed --- and
enter an integer in the FileSize and/or FileAge entry fields,
if you want to use size or age to reduce the filenames found
via the specified mask.

********************
The 'FileType' field:
********************

The FileType field is meant to look for strings in the output
of the 'file' command --- to help distinguish among various types
of GIF file. Unfortunately, the 'file' command returns essentially
the same string whether a GIF file is animated or not.

Example 'file' output: 'GIF image data, version 89a, N x M'
                        where N and M are integers giving the
                        x,y size of the image.

The text 'GIF image data, version 89a' is the same whether
a GIF file is animated or not.

The FileType field is probably not needed, because, by default,
this utility uses a shell script that uses a combination of
the 'file' command and the ImageMagick 'identify' command to
seek out *animated* GIF files.

If you do not put a string in the FileType field, essentially
this utility provides the string 'GIF' as the file-type.
The 'file' command is used to make sure that the files returned
from the 'find'-search are GIF files. And then the ImageMagick
'identify' command is used to select only the animated GIF files.

Just in case the user might want to filter the 'find'
output on a string like '89a' (or some other string)
from the output of the 'file' command, we include the
FileType entry field as a search-filter option.


********************************
ADDING/CHANGING AN AniGIF PLAYER:
********************************

A different image viewer program could be added (or switched with
one that has been supplied) by some simple changes in the Tk script
'find_and_AniGifViewers_FrontEnd.tk'.

Look for places where the strings 'gifview', 'animate',
'RADVARdisplaypgm', 'VARcommand', and 'PARMgeom' occur.


*******************************
SOME AUXILIARY *SHELL* SCRIPTS:
*******************************

The Tk GUI script runs a shell script named 'findAniGIFfiles_forCriteria.sh'
to handle formulating the 'find' command and running it.

That shell script runs a shell script named 'file-identify_aniGIF.sh'
to determine whether a found file is an *animated* GIF file.

***********************
INSTALLING THIS UTILITY:
***********************

The set of scripts for this utility (a Tk script and 2 shell scripts)
could be put in a sub-directory of the user's home directory, such as
\$HOME/apps/tkBatchAniGifViewer.

Then the user can use their desktop system (such as Gnome or KDE) to
set up the Tk script as an icon on the desktop.

Then, whenever the user wants to search a directory hierarchy
for animated-GIF files, the user can click on the icon to startup
the Tk GUI script.
"


##+######################################################
## ADDITIONAL GUI INITIALIZATION section.
##+######################################################

##+#############################################################
## Initialize 'PREV' variables that will be used to determine
## when the user has changed a 'find' criterion.
##+#############################################################

set PREV_ENTRYfileMask ""
set PREV_RADVARlevels ""
set PREV_RADVARcasesense ""
set PREV_RADVARfilesize ""
set PREV_ENTRYfileSize ""
set PREV_RADVARfileage ""
set PREV_ENTRYfileAge ""
set PREV_ENTRYfileType ""


##+#######################################
## Set screensize vars, for use in the
## 'set_window_positions' proc.
##+#######################################

set SCREENwidthPx  [winfo screenwidth .]
set SCREENheightPx [winfo screenheight .]


##+################################################
## Initialize the setting of variable 'VARcommand',
## according to the initial setting of the
## player-program radiobuttons.
##+################################################

set_player_command


##+########################################################################
## Get the directory that this Tk script is in. That will be the
## directory that the 'external' utility shell scripts should be
## in. This Tk directory variable is used to call one of the shell scripts
## --- one to do the 'find'.
##+########################################################################

## FOR TESTING:
#  puts "argv0: $argv0"

# set DIRscripts "."
# set DIRscripts "[pwd]"
# set DIRscripts "$env(HOME)/apps/tkUtils"
  set DIRscripts "[file dirname $argv0]"


##+#############################################################
## Set the directory and filename for a temporary' file that
## will be used in the shell script(s) to hold the list of
## 'found' files.
##+#############################################################

set tempDIR "/tmp"
set outFILE "${tempDIR}/${env(USER)}_tkBatchAniGIFviewer_filenames.lis"


##+#############################################################
## Set the initial directory and filename-mask for the
## filename-mask entry field.
##+#############################################################

set curDIR "$env(HOME)"

## FOR TESTING:
   set curDIR [pwd]

set ENTRYfileMask "$curDIR/*.gif"

## Make sure the end of the filename is in view.

.fRfileMask.entryFILEMASK xview end



Shell script #1 (called by the Tk script) :

And here is the code for the shell script called by this Tk script. This is a wrapper script for the 'find' command.

You can put this script in the same directory with the Tk script. The Tk script includes some code (involving the 'argv0' variable) to determine the location of the shell script by extracting the name of the directory in which the Tk script lies.

 Code for shell-script-1 'findAniGIFfiles_forCriteria.sh' :
#!/bin/sh
##
## SCRIPT NAME: findAniGIFfiles_forCriteria.sh
##
##+#######
## PURPOSE:
##  This script finds a set of user-specified animated-GIF files --- according
##  to a fully-qualified file mask (directory and mask) and about seven other
##  'find' parameters passed to this script.
##
##  This shell script is meant to be issued from the Tk GUI 'wrapper' script
##  'find_and_aniGifViewers_FrontEnd.tk'.
##  That Tk script is to get parameters for the 'find' command
##
##+######
## INPUTS:
##
##  The parameters passed to this script, from the
##  'find_and_aniGifViewers_FrontEnd.tk' Tk GUI script, are:
## 
##   Var1: 'Base'-directory name and file mask. Example:
##          /home/fred/AniGIFs/*elephant*.gif
##
##   Var2: A 'levels' indicator - from a Tk radiobuttons var.
##         (possible values: 'one' or 'all').
##
##   Var3: A 'case-sensitivity' indicator - from a Tk radiobuttons var.
##         (possible values: 'case-sensitive' or 'case-INsensitive')
##
##   Var4: A 'bigger/smaller' indicator from a Tk radiobuttons var
##.        (possible values: 'bigger' or 'smaller')
##
##   Var5: An integer file-size-cutoff value (in KiloBytes)
##         (could be null, indicating no size limitation)
##
##   Var6: An 'older/younger' indicator - from a Tk radiobuttons var.
##.        (possible values: 'older' or 'younger')
##
##   Var7: An integer file-age-cutoff value (in days)
##         (could be null, indicating no age limitation)
##
##   Var8: A file-type string --- typically 'GIF'
##
##   Var9: The name of a (temporary) output file to be used to
##         hold the filenames found by the 'find' command.
##
## In this script, we will put those parameters in shell script variables
## VARfilemask, VARlevels, VARsense,
## VARbigsmall,  VARfilesize,
## VARoldyoung,  VARfileage,
## VARfiletype,  VARoutfile  --- respectively.
##
##+#########################################################################
## MAINTENANCE HISTORY:
## Started by: Blaise Montandon 2014may01 Made this 'findAniGIFfiles_forCriteria.sh'
##                                        shell script by revising shell script
##                                        'findMediaFiles_forCriteria.sh' that
##                                        was used for a script named
##                                        
## Updated by: Blaise Montandon 2014may20 Put call to 'file-identify_aniGIF.sh'
##                                        in the first part of the final 'if'
##                                        statement in addition to the 2nd part.
##+#########################################################################

## FOR TESTING: (to show statements as they execute)
#  set -x

VARfilemask="$1"
VARlevels="$2" 
VARsense="$3"
VARbigsmall="$4"
VARfilesize="$5"
VARoldyoung="$6"
VARfileage="$7"
VARfiletype="$8"
VARoutfile="$9"


## FOR TESTING of this script without the Tk wrapper:
## (For stand-alone testing, change 'if test 1 = 0' to 'if test 1 = 1'.)

if test 1 = 0
then

   # VARfilemask="$HOME/TESTfilesMOVIES/Images/anigif/*.gif"
   VARfilemask="$HOME/TESTfilesMOVIES/Images/anigif/*"

   VARlevels="one"
   # VARlevels="all"

   VARsense="case-sensitive"
   # VARsense="case-INsensitive"

   VARbigsmall="bigger"
   # VARbigsmall="smaller"

   VARfilesize=""

   VARoldyoung="older"
   # VARoldyoung="younger"

   VARfileage=""

     VARfiletype="GIF"
   # VARfiletype=""

   VARoutfile="/tmp/${USER}_tkBatchAniGifViewer_filenames.lis"

fi

## FOR TESTING:
#   echo "VARfilemask: $VARfilemask"


## Simply exit if there is no filemask passed to this script.

if test "$VARfilemask" = ""
then
   exit
fi

DIRNAME=`dirname "$VARfilemask"`
FILEMASK=`basename "$VARfilemask"`

if test "$VARlevels" = "one"
then 
   DEPTHPARM="-maxdepth 1"
else
   DEPTHPARM=""
fi


if test "$VARsense" = "case-sensitive"
then 
   NAMEPARM="-name"
else
   NAMEPARM="-iname"
fi


## The FILESIZE_PARM can be a parameter like
##      -size +${SIZE_MINinBYTES}c
## for the 'find' command.

if test "$VARfilesize" = ""
then
   FILESIZE_PARM=""
else
   SIZEinBYTES=`expr $VARfilesize \* 1000`
   if test "$VARbigsmall" = "bigger"
   then
      # FILESIZE_PARM="-size +${SIZEinBYTES}c"
      FILESIZE_PARM="( -size +${SIZEinBYTES}c -o -size ${SIZEinBYTES}c )"
      ## Note that we do not need to 'escape' the parentheses like we
      ## do in a 'find' command entered at a shell command prompt, because
      ## the parentheses are 'protected' by being within a variable,
      ## rather than being 'exposed' in a command string.
   else
      FILESIZE_PARM="-size -${SIZEinBYTES}c"
   fi
fi


## The FILEAGE_PARM can be a parameter like
##     -mtime +$NDAYS
## for the 'find' command.

if test "$VARfileage" = ""
then
   FILEAGE_PARM=""
else
   if test "$VARoldyoung" = "older"
   then
      FILEAGE_PARM="-mtime +$VARfileage"
   else
      FILEAGE_PARM="-mtime -$VARfileage"
   fi
fi


#############################################################
## Prepare to use the temp filename to hold the filenames.
## Make sure we start with a new (empty) output file.
#############################################################

rm -f "$VARoutfile"


#############################################################
## Get the directory name of this script, to call
## on another script in the same directory as this script.
#############################################################

thisDIR=`dirname $0`

##+#####################################################
## Call on the 'find' command, which puts the
## 'found' filenames, SORTED, in file "$VARoutfile".
##
## We use a '-type d -fprint /dev/stderr' clause in the
## 'find' command to show directories being searched,
## in the 'xterm' that runs this script.
## This gives a kind of 'progress indicator' to let the
## user know how far/fast the 'find' command is progressing. 
##
## We also use the 'tee' command to output 'found' filenames 
## to 'stdout', as a means of providing a 'progress
## indicator' in the 'xterm' that runs this script.
## However, if not many filenames are found and then the
## 'xterm' closes quickly, the user may not get much
## of a progress indicator by this.
##
## The directory-name output is probably going to be
## the better progress indicator in most cases.
##+####################################################
## NOTE: Do not escape the quotes around $FILEMASK.
##       If you do, no filenames are returned.
##+######################################################

## FOR TESTING: (to show the 'find' commands as they execute)
## (When called from within the Tk wrapper script,
##  this output may interfere with proper processing.)
#   set -x

if test "$VARfiletype" = ""
then
   find "$DIRNAME" $DEPTHPARM  \
      \( -type d -fprint /dev/stderr \) , \
      \( -type f  $NAMEPARM "$FILEMASK" \
      $FILESIZE_PARM $FILEAGE_PARM -exec \
      $thisDIR/file-identify_aniGIF.sh "GIF" {} \; \) | \
      sort | tee "$VARoutfile"
else
   find "$DIRNAME" $DEPTHPARM \
      \( -type d -fprint /dev/stderr \) , \
      \( -type f $NAMEPARM "$FILEMASK" \
      $FILESIZE_PARM $FILEAGE_PARM -exec \
      $thisDIR/file-identify_aniGIF.sh "$VARfiletype" {} \; \) | \
      sort | tee "$VARoutfile"
fi


Shell script #2 (called by shell script #1) :

And here is the code for the shell script that selects out GIF files and distinguishes a non-animated GIF file from an animated-GIF file.

You can put this script in the same directory with the Tk script. Shell script #1 includes some code (involving the '$0' variable) to determine the location of the directory containing this shell script (and all 3 scripts) by extracting the name of the directory in which shell-script-1 lies.

 Code for shell-script-2 'file-identify_aniGIF.sh' :
#!/bin/sh
##
## SCRIPT NAME: file-identify_aniGIF.sh
##
##+#######
## PURPOSE:
##  For a file-type string (such as 'GIF') passed in the first argument ($1)
##  and a filename passed in the second argument ($2), this script uses
##   1) the 'file' command to determine if the file is the specified file-type
##  and 
##   2) if so, then uses the ImageMagick 'identify' command determine
##      whether the GIF file is an animated-GIF file.
##
##  If the filename IS an animated-GIF, the filename is echoed to stdout.
##  Otherwise, nothing is sent to stdout.
##
##  This shell script is meant to be issued from a shell script
##  'findAniGIFfiles_forCriteria.sh'.
##  That shell script calls this script in a 'find' command.
##
##+#########################################################################
## MAINTENANCE HISTORY:
## Started by: Blaise Montandon 2014may01                                        
## Updated by: Blaise Montandon 2014
##+#########################################################################

## FOR TESTING: (to show statements as they execute)
#  set -x

FILEtype="$1"
INfile="$2"

##+##########################################################
## Use the 'file' and 'identify' commands to determine
## whether 'INfile' is an animated-GIF file.
##+##########################################################
## Example output from the 'file' command on a GIF file:
## $ file file_middle_name.gif
## file_middle_name: GIF image data, version 89a, 444 x 175
##
## So we can look for 'GIF' after the colon character.
############################################################
## Example output from the 'identify' command on a GIF file:
## identify file_middle_name.gif
## file_middle_name.gif GIF 1024x768 1024x768+0+0 8-bit PseudoClass 256c 208kb 
#################################################################################
## Example output from the 'identify' command on an animated-GIF file:
## $ identify file_middle_name_ani.gif
## file_middle_name_ani.gif[0] GIF 444x175 444x175+0+0 8-bit PseudoClass 32c 4.28kb 
## file_middle_name_ani.gif[1] GIF 16x18 444x175+49+46 8-bit PseudoClass 32c 4.28kb
## etc. for each 'scene' in the animated-GIF file.
##
## So we can look for '[0]' in the first line of output from 'identify'.
##+##############################################################################

## FOR TESTING: (to show statements as they execute)
#  set -x

GIFfilename=`file "$INfile" | grep ":.*$FILEtype" | cut -d: -f1`

if test "$GIFfilename" = ""
then
   exit
fi

ANIGIFcheck=`identify "$GIFfilename" | head -1 | grep '\[0\]'`

if test "$ANIGIFcheck" = ""
then
   exit
else
   echo "$GIFfilename"
fi


INSTALLING THESE SCRIPTS:

This set of 3 scripts could be put in a sub-directory of the user's home directory, such as $HOME/apps/tkBatchAniGIFsPlayer.

Then the user can use their desktop system (such as Gnome or KDE) to set up the Tk script as an icon on the desktop.

Then, whenever the user wants to search a directory hierarchy for animated-GIF files, the user can click on the icon to startup the Tk GUI script.

In fact, here is an image of the 6 icons that I set up on an Ubuntu 9.10 Gnome 2.28 desktop for the 3 'batch-find-and-play' and 3 'play-from-a-playlist' utilities that I mentioned at the top of this page.

sixIcons_forFindAndPlaylistUtils_Img-Media-AniGIF_screenshot_471x132.jpg


SOME POSSIBLE ENHANCEMENTS :

maxdepth N

I indicated near the top of this page that I may change (someday) the 'ONE' radiobutton to an 'N-levels' radiobutton. If that radiobutton is selected, an entry field will be activated where the user can enter a choice of N (with the entry field being initialized with '1'). This capability would be implemented via the '-maxdepth' parameter of the 'find' command.

There is also a '-mindepth' parameter of the 'find' command. But I have never encountered a situation where I felt I needed to use that parameter.

prune

Probably even more useful than the 'maxdepth' capability would be allowing on the GUI for use of the '-prune' parameter of the 'find' command.

To implement the '-prune' feature would require adding yet another entry widget to the GUI --- to allow for entering one or more sub-directory names (or masks).

As I pointed out on the 'find-and-imageViewer' FrontEnd page, I found a simple example that verifies that the 'find' command can prune using 'masks' as well as complete directory names:

  find . -type d -name '.git*' -prune -o -type f -print

---

I leave the 'N-levels' and '-prune' enhancement possiblities to a future date. These two features are similar in that they allow the user avoid 'dropping into' a (large) sub-directory of image files which the user does not wish to process.

I will install these 3 scripts in a $HOME/apps/tkBatchAniGIFsPlayer directory, as suggested above in an 'install' section. I will define an icon on my desktop by which I can start up this GUI and keep testing it in coming months.

Then, if I encounter a need for either of those features ('N-levels' or 'prune'), I may implement them and update the code on this page.

---

There are a lot of parameters available with the 'find' command, but I think I have probably implemented the ones that are most useful to use for this 'multiple-subdirectory aniGIF-file-select-and-display' utility.

That said, I will probably find a '-prune' capability to be quite desirable.


IN CONCLUSION

As I have said on several other code-donation pages on this wiki ...

There's a lot to like about a utility that is 'free freedom' --- that is, no-cost and open-source so that you can modify/enhance/fix it without having to wait for someone else to do it for you (which may be never).

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.


uniquename UPDATE 2014may28:

In using this utility some more since testing it originally, I found that I needed to move some button 'config' statements in the proc 'start_playing_aniGIF_files' so that they do not un-do button 'config' statements performed by the proc 'play_nextN_aniGIF_files'.

I have replaced the Tk script code above with the corrected code.