tkPlayAniGIFsInPlaylist - a front-end for multiple animated-GIF players

uniquename - 2014mar12

In 2013 December through 2014 March, I contributed Tcl-Tk code to 'show' image files --- at

tkImageViewer - for Mask-Selecting Images from a Directory Hierarchy

and at

tkShowImageFilesInPlaylist - a front-end for multiple image viewers.

And I contributed Tcl-Tk code to 'play' media (video and/or audio) files at

tkMediaPlayer - to Batch-Select-and-Play Movie & Audio files in a Directory Hierarchy

and

tkPlayMediaFilesInPlaylist - a front-end for multiple media-players.

The two utilities for which 'Directory Hierarchy' is in the title were actually 'front-ends' for the 'find' command as well as for show/play programs.

The two utilities for which 'Playlist' is in the title were for showing/playing files in the order in which they are placed in a 'playlist' file, which has a simple format that can be created with a text editor.

Soon after those code contributions, I realized that I could have some use for a similar pair of utilities to play batches of animated GIF files.

On my (Linux) computers, I have a couple of 'light-weight' players for animated GIF files --- the ImageMagick 'animate' program and the 'gifview' program, which usually comes in a package with the 'gifsicle' program that can be used to make animated GIF files.

I decided to make the 'playlist' version of the 2 animated-GIF scripts first.

---

THE GOALS

My goals for the Tcl-Tk script were:

  - Provide a GUI for selecting a 'playlist' of animated-GIF files.

  - Allow the user to select an animated-GIF 'player' program from among multiple choices.

  - Since animated GIF files are ordinarily rather small (typically no more than about
    100 pixels by 100 pixels), allow the user to play multiple animated-GIFs at a time
    --- placed in a 'grid' on the monitor screen.

  - provide a 'Launch' button to start showing the animated-GIFs, in the order of the
    filenames within the playlist file.

  - provide a 'Stop' button for 'interrupting' the display of a (long) sequence
    of animated-GIFs.

  - like on the four utilties listed above for static-image files and 'media' files,
    provide 'Count' and 'List' buttons on the GUI to allow for quickly counting
    and displaying the (full) filenames of the 'aniGIF' files in the playlist file.

---

THE GUI LAYOUT

Like for the 'image-viewer' and 'media-player' utilities, I made a 'text-sketch' for the GUI for this 'play-aniGIF-files-in-a-playlist' utility:

  ---------------------------------------------------------------------------
  Play *Animated GIF files* Files of a *Playlist* ...  with a choice of players
  [window title]
  --------------------------------------------------------------------------------

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

  Playlist Filename: _____________________________________________________  {Browse...}

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

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

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

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).

---

GUI Components

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

   -  8 button widgets
   -  3 label widgets
   -  1 entry widget
   -  9 radiobutton widgets in 2 groups
   -  0 checkbutton widgets
   -  0 scale widgets
   -  0 listbox widgets

THE PLAYLIST FILE FORMAT

The intent of this utility is to allow the user to easily specify files to be viewed ... by building a simple 'playlist'.

The playlist file for this utility contains three types of lines:

  1 - Comment lines may be indicated in the file by a # sign
      in column 1.

  2 - Lines that contain a fully-qualified directory name.
      Examples:  /data/aniGIFs/smileys   OR
                 $env(HOME)/smileys      OR
                 /home/fred/smileys
      These lines must start with either slash (/) or $.

  3 - Lines that contain a relative filename (relative to the
      previous directory name).
      Example: smiley_dancing_15x15_ani.gif
      These lines DO NOT start with either slash (/) or $ or #.

The first non-comment line of the playlist file should be a directory name.

There can be more than one directory name in the file.

Each directory-name-line is followed by names of image files that are in the specified directory.

A SAMPLE animated-GIFs playlist file:

 # A description of this file could go here.
 #
 # First directory:  (arrow aniGIFs)
 $env(HOME)/aniGIFs/arrows
 arrow_down_137x120_ani.gif
 arrows3downward_153x205_ani.gif
 #
 # Second directory:  (smiley aniGIFs)
 $env(HOME)/aniGIFs/smileys
 smileyStar_doingToeTouches_100x100_transp_ani.gif
 smiley_blinking_120x120_transp_ani.gif
 smileysThree_jumpingRope_200x70_transp_ani.gif
 smileys_2onSeesaw_161x120_transp_ani.gif
 smileys_doing_the_wave_225x33_transp_ani.gif

FRONT END FOR OPTIONS FOR THE PLAYERS

This 'Front End' Tk script is essentially a 'front end' for the animated-GIF 'player' programs that are offered via radiobuttons on the GUI.

A second set of radiobuttons allow for placing several animated-GIFs on the screen at the same time. In fact, the second set of radiobuttons allow the user to select a 'grid layout' for the 'player windows'.

Example Layouts (and number of player-windows at a time):

   - 1x1(1)
   - 2x1(2)
   - 3x1(3)
   - 2x2(4)
   - 3x2(6)
   - 3x3(9)
   - 4x3(12)

In the future, if there are some options (of at least one of the aniGIF 'player' programs) that would seem nice to implement, additional widgets could be added to the GUI to support those choices.

---

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 make playing animated-GIFs that are specified in a playlist file (with a very simple format) --- and with multiple choices of the 'player' --- an easy process (a small set of mouse clicks).

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).


SCREENSHOT OF THE GUI

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

tkPlayAniGIFsInPlaylist_gray_fontComicSans_screenshot_700x136.jpg

Note that there are two radiobuttons that allow you to choose the 'player' to use --- and about seven 'layout' radiobuttons.

---

TYPICAL SEQUENCE OF OPERATIONS WITH THE GUI

STEP 1:

You can use the 'Browse...' button to retrieve a full filename to the playlist filename entry field.

STEP 2:

Before clicking on the 'LaunchPlayerJob' button, you can change *radiobutton* settings as needed --- for

- a player program

and

- layout of player windows on the screen --- 1x1(1), 2x1(2), 3x1(3), 2x2(4), 3x2(6), 3x3(9), and 4x3(12).

*THEN* click on the 'Launch' button.

STEP 3:

Clicking on the 'Next N' button causes the next N animated GIF files to be played.

The user has the option whether to close the currently running aniGIF 'player' windows before starting the next N windows.

STEP 4:

Keep doing 'Step 3', until all aniGIFs of the playlist are shown --- or until the 'Stop' button or 'Exit' button is used.

I tried to make the operation of the GUI rather general in the sense that the user CAN change the choice of 'player' and/or 'layout' at any point after a display of N animated-GIFs.

---

The other buttons:

Clicking on the 'Stop' button sets an indicator. The next time the user clicks on the 'Next N' button, the user will be advised that no more of the aniGIF's in the playlist file will be shown.

After a 'Stop', the user can use the 'Launch' button to start another playing sequence via a playlist file.

---

Optionally, if you want to check the number or names of the image files specified in the 'playlist' file, then, after selecting a playlist file, click on the 'CountAniGIFs' or 'ShowAniGIFfilenames' button.

---

BREADTH AND FLEXIBILITY FEATURES OF THIS UTILITY

Note that this utility has the flexibility of a 'playlist' approach. The user is given an opportunity to SELECT the files to be shown and is given the opportunity to choose the ORDER in which the selected image files are shown.

This utility is oriented toward showing image files which may be scattered throughout various PARENT DIRECTORIES and THEIR SUB-DIRECTORIES --- in a play-back ORDER DETERMINED BY THE USER (that is, determined by the order of directories and files in the playlist file).


The code

Below, I provide the Tk script code for this 'play-aniGIFs-in-a-playlist' 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-for-widgets, widget-geometry-parms,
     text-array-for-labels-etc, win-size-control).

  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 use of 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 widget expands/contracts 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.

The following image is an example of setting a different 'palette' for the GUI and a different 'variable-width' font for the button and label widgets of the GUI.

tkPlayAniGIFsInPlaylist_lightBrown_fontAugie_screenshot_790x136.jpg

By the way, the 'Next' and 'Stop' buttons are activated when the 'Launch' button is clicked.


Some features in the code

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

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

The main procs are

  'get_playlist_filename'    - called by the 'Browse...' button
                               next to the filename entry field.

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

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

  '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 called via the 'CountAniGIFs' and
                                    and 'ShowAniGIFfilenames' buttons.

Like with the 'imageViewer-Playlist' and 'mediaPlayer-Playlist' Tk GUI utilities, 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

---

One of the trickiest things about this GUI involved structuring the procs in such a way as to allow the user a lot of flexibility, such as

- ability to change the 'player' after any display of N aniGIFs.

- ability to change the 'layout' after any display of N aniGIFs.

- ability to stop further display after any display of N aniGIFs --- and start displaying again --- with the same playlist or a different one.

---

In addition, there are some 'cute' features of the GUI. For example, when the user clicks on a 'layout' radiobutton, the integer in the 'Next N' button changes accordingly.


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 'binge-watch' the last season of 'Breaking Bad'.


 Code for Tk script 'playAniGIFfilesInPlaylist_FrontEnd.tk' :
#!/usr/bin/wish -f
##
## Tk SCRIPT NAME: playAniGIFfilesInPlaylist_FrontEnd.tk
##
##+#######################################################################
## PURPOSE:  This Tk script provides a GUI for selecting a 'playlist' of
##           animated GIF files. The animated GIF files may be scattered
##           throughout various directories -- such as sub-directories of a
##           web site --- OR sub-directories of a home directory --- OR
##           sub-directories on multiple disk drives mounted on a computer ---
##           OR sub-directories of a removable storage device mounted on
##           a computer.
##
##           Besides selecting the playlist file, this utility
##           allows the user to select a 'player' program from among
##           multiple choices --- and allows the user to specify a 'layout'
##           for the GIF files on the screen (i.e. this utility allows
##           multiple GIF's to be playing at the same time).
##
##           Example Layouts (and number of player-windows at a time):
##                - 1x1(1)
##                - 2x1(2)
##                - 3x1(3)
##                - 2x2(4)
##                - 3x2(6)
##                - 3x3(9)
##                - 4x3(12)
##
##+#########################
## TYPICAL OPERATIONAL STEPS:
##
##           After specifying/selecting the playlist file and specifying
##           the 'player' and 'layout', then the user can click on a 'Launch'
##           button to start playing the animated GIF files, in the order
##           of the filenames within the playlist 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 playlist 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 playlist file.
##
##           The user can click on the 'Help' button for a description
##           of this utility (similar to this description).
##
##+######################
## AniGIF PLAYER PROGRAMS:
##
##           The player program is chosen via radiobutton widgets on 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.
##
##           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.
##
##           The GUI is intended to employ the main (most useful) aniGIF
##           'player' program options available -- without the user needing
##           to reference the 'man animate' or 'man gifview' commands --
##           and other 'animate'/'gifview' documentation, for example,
##           via web searches.
##
##+#################
## THE PLAYLIST FILE:
##
##           The intent of this utility is to allow the user to easily
##           specify files to be played ... by building a simple 'playlist'.
##
##           The playlist file contains three types of lines:
##
##             1 - Comment lines may be indicated in the file by a # sign
##                 in column 1.
##
##             2 - Lines that contain a fully-qualified directory name
##                 or a 'dot' directory name.
##                 Examples: /data/aniGIFs/smileys or
##                           $env(HOME)/smileys    or
##                           /home/fred/smileys
##                           .                      or
##                           ./other
##                 These lines must start with either slash(/) or $ or dot(.).
##
##                 The dot can be used to represent the directory in which
##                 the playlist file lies. This useful when putting the playlist
##                 file in a directory of animated GIFs and using the dot to
##                 represent the directory of the aniGIF files. The directory of
##                 aniGIFs may have sub-directories of aniGIFs, as indicated
##                 by the last example. The dot allows for moving/copying the
##                 aniGIFs (with the playlist file) to different directories
##                 without having to change the internals of the playlist file.
##
##             3 - Lines that contain a relative filename (relative to the
##                 previous directory name).
##                 Example: smiley_dancing_15x15_ani.gif
##
##                 These lines SHOULD NOT start with either slash(/) or $ or dot(.)
##                 or # --- although a # could be used to comment out a filename.
##
##           The first non-comment line of the playlist file should be a
##           directory name.
##
##           There can be more than one directory name in the file.
##
##           Each directory-name-line is followed by names of aniGIF files
##           that are in the specified directory.
##
##+###################################
## NOTEWORTHY FEATURES OF THIS UTILITY:
##
##           Most light-weight animated GIF 'players, like 'animate' and
##           'gifview', do not support a playlist feature.
##
##           This utility is oriented toward using a simple, 'open', common
##           playlist format that can be used with any of these animated GIF
##           players --- via this Tk GUI script.
##
##           Note that in building the playlist file, the user can SELECT
##           animated GIF files which may be scattered throughout various PARENT
##           DIRECTORIES and THEIR SUB-DIRECTORIES --- and place the filenames
##           in a play-back ORDER DETERMINED BY THE USER (that is, determined
##           by the order of directories and files in the playlist file).
##
##+#################
## THE GUI WIDGETS:
##
##     The options available to the user are compactly indicated
##     by the following 'sketch' of the GUI:
##
##  ------------------------------------------------------------------------------------------
##  Play *Animated GIF files* Files of a *Playlist* ...  with a choice of players
##  [window title]
##  ------------------------------------------------------------------------------------------
##
##  {Exit} {Help} {LaunchPlayerJob} {Next N} {Stop (no more files)}    {CountAniGIFs} {ShowAniGIFfilenames}
##
##  Playlist Filename: _____________________________________________________  {Browse...}
##
##  Player Program:  O 1 - ImageMagick 'animate'    O 2 - 'gifview'
##
##  LayoutOnScreen: O 1x1(1)   O 2x1(2)  O 3x1(3)  O 2x2(4)  O 3x2(6)  O 3x3(9)  O 4x3(12)
##
##  -------------------------------------------------------------------
##
## In the above sketch of the GUI:
##
## 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).
##
##+##############
## GUI components:
##
## From the GUI 'sketch' above, it is seen that the GUI consists of
## about
##
##   -  8 button widgets
##   -  3 label widgets
##   -  1 entry widget
##   -  9 radiobutton widgets in 2 groups
##   -  0 checkbutton widget
##   -  0 scale widgets
##   -  0 listbox widgets
##
##+#####################################################################
## CALLED BY:  This script could be put in a sub-directory of the
##             user's home directory, such as $HOME/apps/tkPlayAniGIFsInPlaylist.
##
##             Then the user can use their desktop system (such as
##             Gnome or KDE) to set up the script as an icon on the
##             desktop. Then the user can click on the icon to
##             startup the script.
##+########################################################################
## STRUCTURE OF THIS CODE:
##
##  0) Set general window parms (win-name, win-position, win-color-scheme,
##     fonts-for-widgets, widget-geom-parms, text-array-for-labels-etc,
##     win-size-control).
##
##  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
##       'fRfile'          for a playlist (directory-and-filename) entry field
##       'fRdisplayPgm'    for several radiobuttons, with a label
##       'fRwindowsLayout' for several radiobuttons, with a label
##
##  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_playlist_filename'    - called by the 'Browse...' button
##                               next to the filename entry field.
##
##  'count_aniGIF_files'       - called by the 'CountAniGIFs' button.
##
##  'show_aniGIF_filenames'    - called by the 'ShowAniGIFfilenames' button.
##
##  '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 called via the 'CountAniGIFs' and
##                                    and 'ShowAniGIFfilenames' buttons.
##
##     For other 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 2014mar11 Started development, on Ubuntu 9.10,
##                                        based on the code of some Tk scripts
##                                        of mine that contained most of the
##                                        widgets needed. Got the GUI up, with
##                                        the procs dummied out.
## Updated by: Blaise Montandon 2014mar12 Prepared the procs for testing.
## Updated by: Blaise Montandon 2014apr14 Added 'file exists'and RETcode checks
##                                        in proc 'play_nextN_aniGIF_files'. 
##                                        Changed initial default from
##                                        3x2 to 2x1.
## Updated by: Blaise Montandon 2014apr22 Added capability to use "." at the
##                                        start of any directory name in the
##                                        playlist file --- to represent the
##                                        directory in which the playlist file
##                                        lies. Changed <KeyPress> to <KeyRelease>
##                                        in binding on filename entry field.
## Updated by: Blaise Montandon 2014may22 Added 'catch' to close of file,
##                                        in 4 places. Replaced var 'fileID' by
##                                        'fileIDcount','fileIDshow','fileIDplay'.
## Updated by: Blaise Montandon 2014may26 1) 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'.
##                                        2) Added 'eval' to a 'set' statement in proc
##                                        'show_aniGIF_filenames' and to a 'set'
##                                        statement in proc 'play_nextN_aniGIF_files'.
##+#######################################################################

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

wm title    . "Play *Animated GIF files* Files of a *Playlist* ...  with a choice of players"

wm iconname . "PlayAniGIFsInPlaylist"

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

  set RGBpalette "#e0e0e0" ;# gray
# set RGBpalette "#ffffff" ;# white
# set RGBpalette "#000000" ;# black
  set RGBpalette "#ffcc88" ;# light-brown
# set RGBpalette "#ccaa44" ;# darker-brown
# set RGBpalette "#00ccff" ;# blue-green

tk_setPalette "$RGBpalette"

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

#    -family {augie} \
#    -family {comic sans ms} \
#    -family {dejavu sans} \
#    -family {droid sans} \
#    -family {vag round} \
#    -family {vtcsundaykomix} \
#    -family {waree} \
#    -family {whackadoo upper} \
#    -family {windsor} \
#    -family {wetalmorker} \
#    -family {y2k neophyte} \

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_radbutt 0
set PADYpx_radbutt 0
set BDwidthPx_radbutt 2
set RELIEF_radbutt_hi "raised"


## CHECKBUTTON widget geom settings:

# set PADXpx_chkbutton 0
# set PADYpx_chkbutton 0
# set BDwidthPx_chkbutton 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 '.fRfile' frame:

set aRtext(labelFILENAME) "Playlist Filename:"
set aRtext(buttonBROWSE)  "Browse..."


## For '.fRdisplayPgm' frame:

set aRtext(labelDISPLAYPGM) "Player Program:"
set aRtext(radbuttDPGM1)    "1 - ImageMagick 'animate'"
set aRtext(radbuttDPGM2)    "2 - '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)"

## 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 '.fRfile'          frame
##      1 char   high for the '.fRdisplayPgm'    frame
##      1 char   high for the '.fRwindowsLayout' frame
##    --------
##      4 chars  high for the 4 frames
##+#####################################################################

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

set minWidthPx [font measure fontTEMP_varwidth \
   " $aRtext(buttonEXIT) $aRtext(buttonHELP) $aRtext(buttonLAUNCH) \
$aRtext(buttonNEXT) $aRtext(buttonSTOP1) $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 --- for
##      1 char   high for the '.fRbuttons'       frame
##      1 char   high for the '.fRfile'          frame
##      1 char   high for the '.fRdisplayPgm'    frame
##      1 char   high for the '.fRwindowsLayout' frame
##    --------
##      4 chars  high for the 4 frames

set charHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {4 * $charHeightPx}]


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

set minWinHeightPx [expr {28 + $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, Launch, ... buttons
##       'fRfile'          for a playlist filename entry field
##       'fRdisplayPgm'    for several radiobuttons, with a label
##       'fRwindowsLayout' for several 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 .fRfile        -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRfile        -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


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

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

pack .fRfile \
   -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


##+################################################################
##+################################################################
## 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 \
   -bd $BDwidthPx_button \
   -relief raised \
   -command {exit}

button .fRbuttons.buttHELP \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -bd $BDwidthPx_button \
   -relief raised \
   -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 \
   -bd $BDwidthPx_button \
   -relief raised \
   -command {count_aniGIF_files}

button .fRbuttons.buttPRINT \
   -text "$aRtext(buttonPRINT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -bd $BDwidthPx_button \
   -relief raised \
   -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 '.fRfile' frame -- DEFINE 1 LABEL widget,
## 1 ENTRY widget, and 1 BUTTON widget.
##+########################################################

label .fRfile.labelFILENAME \
   -text "$aRtext(labelFILENAME)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

entry .fRfile.entryFILENAME \
   -textvariable ENTRYfilename \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -bd $BDwidthPx_entry \
   -relief sunken


## Set an initial value for the entry var.

set playlistDIR "$env(HOME)"
# set playlistDIR "$env(HOME)/MyAniGIFplaylists"
# set playlistDIR "/data/playlists/aniGIFs"

## FOR TESTING:
     set playlistDIR [pwd]

set ENTRYfilename "$playlistDIR/aniGIF_playlist.lis"

## Put the end of the filename in view.

.fRfile.entryFILENAME xview end


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

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

pack  .fRfile.labelFILENAME \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfile.entryFILENAME \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRfile.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 \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief  $RELIEF_label_lo


## DEFINE radiobuttons for aniGIF 'player' programs :

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

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

## Change 0 to 1 if you want to activate this radiobutton.
if {0} {
radiobutton  .fRdisplayPgm.radbuttDPGM3 \
   -text "$aRtext(radbuttDPGM3)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARdisplaypgm \
   -value "3" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi
}

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

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

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

#      .fRdisplayPgm.radbuttDPGM3 \


##+########################################################
## 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 \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo


## DEFINE radiobuttons for layout options :

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

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

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

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

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

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

radiobutton  .fRwindowsLayout.radbuttLAYOUT4x3 \
   -text "$aRtext(radbuttLAYOUT4x3)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARlayout \
   -value "4x3" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -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


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

##+#####################################################################
##+#####################################################################
## DEFINE BINDINGS:
##        button1-release bindings on some radiobutton widgets
##  and
##        key-press bindings on an entry widget.
##
## (Someday a Return-key binding on the entry widget???)
## (  bind .fR?????.???widget???  <Return>  {??proc??} )
##+#####################################################################

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


## As the user types in the filename entry field (KeyRelease events), make sure
## the area near the insertion cursor or the character entry is showing.
## (Strange? '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 .fRfile.entryFILENAME <KeyRelease> \
   {.fRfile.entryFILENAME xview insert}

## FOR TESTING:
#   bind .fRfile.entryFILENAME <KeyPress> {
#      set entFILENAMExview [.fRfile.entryFILENAME xview]
#      puts ".fRfile.entryFILENAME 'offset' and 'span': $entFILENAMExview"
#   }

## An alternative?
# bind .fRfile.entryFILENAME <KeyRelease> \
#     {.fRfile.entryFILENAME xview moveto 1.0}


##+#####################################################################
##+#####################################################################
## DEFINE PROCEDURES:
##
##  'get_playlist_filename'    - called by the 'Browse...' button
##                               next to the filename entry field.
##
##  'count_aniGIF_files'       - called by the 'CountAniGIFs' button.
##
##  'show_aniGIF_filenames'    - called by the 'ShowAniGIFfilenames' button.
##
##  '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 called via the 'CountAniGIFs' and
##                                    and 'ShowAniGIFfilenames' buttons.
##+#####################################################################
##+#####################################################################

##+#####################################################################
## PROC:  'get_playlist_filename'
##+#####################################################################
## PURPOSE: To get the fully-qualified name of a file and put the
##          name into global var 'ENTRYfilename'.
##
##    Note: The user may change the filename (after the end of the
##          directory name) to change it to a different filename
##          in the same directory. For example: change playlist1.lis
##          to playlist2.lis.
##
##          Furthermore, the user may completely change both
##          the directory name and the filename by editing
##          the text in the entry field.
##
## CALLED BY: the '-command' option of the 'Browse ...' button.
##+#####################################################################

proc get_playlist_filename {} {

   global ENTRYfilename env playlistDIR

   #############################################
   ## Get a 'aniGIF-playlist' file name.
   #############################################

   set fName [tk_getOpenFile -parent .  \
      -title "Select Directory-and-Filename - of a playlist-file" \
      -initialdir "$playlistDIR" ]

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

   ################################
   ## Check if fName var is empty.
   ################################

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

   #####################################################
   ## Put $fName in ENTRYfilename. Reset the playlistDIR var.
   #####################################################

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

      ## Put the end of the filename in view.
      .fRfile.entryFILENAME xview end

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

}
## END OF proc 'get_playlist_filename'


##+###################################################################
## 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_img_filename' and 3 'aniGIF' procs below.
##
##+##################################################################

proc get_chars_before_last { char in strng } {

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

   ## 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 'get_chars_before_last' PROCEDURE


##+###################################################################
## 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 'get_chars_after_last' PROCEDURE



##+#############################################################
## PROC:  'count_aniGIF_files'
##+#############################################################
## PURPOSE: For a given playlist file, counts the
##          aniGIF filenames in the playlist file ---
##          by counting the non-empty lines that do NOT
##          start with slash (/) or $ or #.
##
## CALLED BY: the 'CountAniGIFs'  button
##+#############################################################

proc count_aniGIF_files {} {

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

   global ENTRYfilename env

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

   set ENTRYfilename [string trim "$ENTRYfilename"]

   if {![file exists "$ENTRYfilename"]} {
      popup_msgVarWithScroll .topCount \
         "Filename in entry field does not exist."
      return
   }

   ###############################################################
   ## 'count' logic goes here.
   ###############################################################
   ## Prepare for counting aniGIF files in the playlist file.
   ###############################################################

   set fileIDcount [open $ENTRYfilename]

   set NaniGIFfiles 0

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

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

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and, optionally,
      ## get its length.
      ############################################################

      # set lineLen [gets $fileIDcount line]

      gets $fileIDcount line

      ###########################
      ## GET FIRST CHAR of line.
      ###########################

      set FIRSTchar [string index "$line" 0]

      ##############################################################
      ## IF A COMMENT LINE, SKIP THIS LINE ... i.e. read next line.
      ##############################################################

      if { "$FIRSTchar" == "#" } {continue}

      #################################################################
      ## IF A DIRECTORY LINE, (set the anigifDIR var and) read next line.
      #################################################################

      if { "$FIRSTchar" == "/" || "$FIRSTchar" == "$"  || \
           "$FIRSTchar" == "." } {
         # set anigifDIR "[string trim $line]"
         ## Since we are just counting the aniGIF's, we do not really
         ## need to keep track of the current aniGIFs directory.
         continue
      }

      #################################################################
      ## IF A RELATIVE-FILENAME LINE, INCREMENT COUNT & read next line.
      #################################################################

      set EMPTYLINEcheck "[string trim $line]"

      if { "$EMPTYLINEcheck" == "" } {continue}

      incr NaniGIFfiles

   }
   ## END OF  while {[eof $fileIDcount] == 0}

   catch {close $fileIDcount}

   ###############################################################
   ## DISPLAY THE COUNT in a small popup Tk window.
   ###############################################################

   popup_msgVarWithScroll .topCount \
      "$NaniGIFfiles aniGIF filenames are in the playlist file ---
[get_chars_after_last "/" in "$ENTRYfilename"]
in directory
[get_chars_before_last "/" in "$ENTRYfilename"]"

}
## END OF proc 'count_aniGIF_files'


##+#############################################################
## PROC:  'show_aniGIF_filenames'
##+#############################################################
## PURPOSE: For a given playlist file, shows the
##          aniGIF filenames in the playlist file ---
##          in a Tk popup window with a scrollable text widget.
##
## CALLED BY: the 'ShowAniGIFfilenames'  button
##+#############################################################

proc show_aniGIF_filenames {} {

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

   global ENTRYfilename env


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

   set ENTRYfilename [string trim "$ENTRYfilename"]

   if {![file exists "$ENTRYfilename"]} {
      popup_msgVarWithScroll .topCount \
         "Filename in entry field does not exist."
      return
   }

   ######################################################
   ## Store the directory of this playlist file, to use
   ## below in place of "." in a directory-name line.
   ######################################################

   set thisDIR [file dirname "$ENTRYfilename"]


   ###############################################################
   ## Logic goes here to SHOW THE AniGIF FILENAMES of
   ## the 'playlist file'.
   ###############################################################
   ## Prepare for getting aniGIF filenames in the playlist file.
   ###############################################################

   set fileIDshow [open $ENTRYfilename]

   set NaniGIFfiles 0

   set TEXTfilenames ""

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

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

      ############################################################
      ## GET THE NEXT LINE (up to a line feed) --- and, optionally,
      ## get its length.
      ############################################################

      # set lineLen [gets $fileIDshow line]

      gets $fileIDshow line

      ###########################
      ## GET FIRST CHAR of line.
      ###########################

      set FIRSTchar [string index "$line" 0]

      ##############################################################
      ## IF A COMMENT LINE, SKIP THIS LINE ... i.e. read next line.
      ##############################################################

      if { "$FIRSTchar" == "#" } {continue}

      #################################################################
      ## IF A DIRECTORY LINE starting with "/" or "$",
      ## SET THE 'anigifDIR' VAR and read next line.
      #################################################################

      if { "$FIRSTchar" == "/" || "$FIRSTchar" == "$"  } {
         set anigifDIR "[string trim $line]"
         continue
      }

      #################################################################
      ## IF A DIRECTORY LINE starting with ".",
      ## SET THE 'anigifDIR' VAR and read next line.
      #################################################################

      if { "$FIRSTchar" == "."  } {
         set lengthOFline [string length "$line"]
         if {$lengthOFline > 1} {
            set restOFline [string range "$line" 1 $lengthOFline]
            set anigifDIR "${thisDIR}$restOFline"
         } else {
            set anigifDIR "$thisDIR"
         }
         continue
      }

      #################################################################
      ## IF A RELATIVE-FILENAME LINE, add the full filename to the
      ## 'TEXTfilenames' var ... and read next line. 
      ##       We need the 'eval' on the 'set' statement to convert
      ##       $env(HOME), if it is used, to a directory name.
      #################################################################

      set EMPTYLINEcheck "[string trim $line]"

      if { "$EMPTYLINEcheck" == "" } {continue}

      eval set aniGIFfilename "$anigifDIR/$EMPTYLINEcheck"

      set TEXTfilenames "$TEXTfilenames
$aniGIFfilename"

      incr NaniGIFfiles

   }
   ## END OF  while {[eof $fileIDshow] == 0}

   catch {close $fileIDshow}


   ###############################################################
   ## DISPLAY THE AniGIF FILENAMES in a popup Tk window.
   ###############################################################

   popup_msgVarWithScroll .topList \
      "For the playlist file
[get_chars_after_last "/" in "$ENTRYfilename"]
in directory
[get_chars_before_last "/" in "$ENTRYfilename"],

the $NaniGIFfiles full-filenames of the aniGIF files in the playlist file
are as follows:

$TEXTfilenames"

}
## 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: For a given playlist file, starts plays the aniGIF
##          files from the playlist file --- by opening the
##          playlist file.
##
##          Then uses the 'play_nextN_aniGIF_files' proc to
##          read the first $N2read files from the playlist file,
##          indicated by directory names and 'relative' aniGIF
##          filenames in the playlist file.
##
## CALLED BY: the 'LaunchPlayerJob'  button
##+#############################################################

proc start_playing_aniGIF_files {} {

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

   global STOPvar0or1 aRtext ENTRYfilename fileIDplay NaniGIFfiles 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)"

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

   set ENTRYfilename [string trim "$ENTRYfilename"]

   if {![file exists "$ENTRYfilename"]} {
      popup_msgVarWithScroll .topCount \
         "Filename in entry field does not exist."
      return
   }
   
   ######################################################
   ## Store the directory of this playlist file, to use
   ## below in place of "." in a directory-name line.
   ######################################################

   set thisDIR [file dirname "$ENTRYfilename"]


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

   set fileIDplay [open "$ENTRYfilename"]

   ##################################################
   ## 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 playlist file is closed).
   #######################################################

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

   #########################################################
   ## Call the proc to read and play the next N aniGIF files.
   #########################################################
    
   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 the 'layout' radiobuttons), this proc
##          reads the next $N2read files from the previously
##          opened playlist file. The full filenames are built
##          using directory names and 'relative' aniGIF
##          filenames in the playlist file.
##
##          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 the playlist file before exiting.
##
##          The global variable 'fileIDplay' is used to do the
##          the file reads -- and 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 playlist file
##        reading (done only by this proc) stops:
##          1) If the STOP button has been pressed (STOP var set to 1).
##          2) We encounter end-of-file in reading the playlist
##             file with this proc.
##          3) We have read $N2read files with this proc.
##         We want to close the playlist 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 fileIDplay env N2read aRxpositions aRypositions \
      VARcommand RADVARdisplaypgm aRtext NaniGIFfiles anigifDIR ENTRYfilename \
      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 'fileIDplay' is closed.
   ############################################################

   if { $STOPvar0or1 == 1 } {

      catch {close $fileIDplay}

      ## FOR TESTING:
      if {0} {
         puts "At PROC 'play_nextN_aniGIF_files' - close playlist file:"
         puts "fileIDplay: $fileIDplay"
         puts "catchRESULT: $catchRESULT"
      }

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

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

      #########################################################
      ## Reset the 'stop' button 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 a playlist file, again).
      ##########################################################

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

      return
   }

   ###############################################################
   ## 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 playlist
   ##       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 the
   ## playlist file. This loop reads NO MORE THAN $N2read
   ## aniGIF files from the playlist 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 $fileIDplay]}'.
   ###################################################################

   set READcnt 0

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

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

      # set lineLen [gets $fileIDplay line]

      gets $fileIDplay line

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

      ###########################
      ## Get first char of line.
      ###########################

      set FIRSTchar [string index "$line" 0]

      ##############################################################
      ## If a comment line, skip this line ... i.e. read next line.
      ##############################################################

      if { "$FIRSTchar" == "#" } {continue}

      ###################################################################
      ## If a directory line starting with "/" or "$",
      ## SET THE 'anigifDIR' VAR and read next line.
      ###################################################################

      if { "$FIRSTchar" == "/" || "$FIRSTchar" == "$"  } {
         set anigifDIR "[string trim $line]"
         continue
      }

      #################################################################
      ## IF A DIRECTORY LINE starting with ".",
      ## SET THE 'anigifDIR' VAR and read next line.
      #################################################################

      if { "$FIRSTchar" == "."  } {
         set lengthOFline [string length "$line"]
         if {$lengthOFline > 1} {
            set restOFline [string range "$line" 1 $lengthOFline]
            set anigifDIR "${thisDIR}$restOFline"
         } else {
            set anigifDIR "$thisDIR"
         }
         continue
      }

      #################################################################
      ## At this point, we have eliminated 2 line types --- comment
      ## lines and directory-name lines. So this line must be a
      ## relative-filename line.
      ##
      ## If the relative-filename line is empty, return to the top
      ## of this 'while' loop to read the next aniGIF filename.
      #################################################################

      set EMPTYLINEcheck "[string trim $line]"

      if { "$EMPTYLINEcheck" == "" } {continue}

      ##################################################################
      ## Set the fully-qualified name of the aniGIF file and
      ## check if it exists.
      ##       We need 'eval' on the 'set' statement to convert
      ##       $env(HOME), if it is used, to a directory name.
      ##################################################################

      eval set aniGIFfilename "$anigifDIR/$EMPTYLINEcheck"

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

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

      #########################################################################
      ## We use READcnt to help determine how many aniGIFs to show at one time.
      #########################################################################

      incr READcnt
      incr NaniGIFfiles


      ###################################################################
      ## At this point the relative-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 "anigifDIR (directory)      :    $anigifDIR"
          puts "EMPTYLINEcheck (filename): $EMPTYLINEcheck"
          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 playlist 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  'while {[eof $fileIDplay] == 0}' LOOP

   #######################################################
   ## Close the playlist file when the while loop is done.
   #######################################################

   catch {close $fileIDplay}

   ## FOR TESTING:
   if {0} {
      puts "At PROC 'play_nextN_aniGIF_files' - close playlist file:"
      puts "fileIDplay: $fileIDplay"
      puts "catchRESULT: $catchRESULT"
   }

   ##########################################################
   ## Disable the 'Next' & 'Stop' buttons. Enable the 'Launch'
   ## button (to allow for starting a playlist 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 playlist file ---
[get_chars_after_last "/" in "$ENTRYfilename"]
in directory
[get_chars_before_last "/" in "$ENTRYfilename"]

$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 'Play Animated GIF Files in a Playlist File' utility **

    ( essentially a 'front end' for MULTIPLE aniGIF-player programs )

This utility provides a GUI for selecting a 'playlist' of animated GIF
files. The animated GIF files may be scattered throughout various
directories --- such as sub-directories that store the files for a
web site --- OR sub-directories of a home directory ---
OR sub-directories on multiple disk drives mounted on a computer ---
OR sub-directories on a removable storage device mounted on a computer.

Besides selecting the playlist file, this utility allows the user
to select an animated-GIF-file 'player' program from among
multiple choices --- and allows the user to select a 'layout'
of the player windows on the screen.

That is, this utility facilitates playing MULTIPLE animated-GIF
files 'at a time'. (Since animated GIF files are frequently no more
than a few hundred pixels horizontally and vertically, it is
possible to fit several of them on the screen without overlap.)

This utility provides 'layout' choices such as the following.

Configuration         Number of
of the Windows on      Windows
the monitor screen    at one time (N)
-------------------   -----------
    1x1                    1
    2x1                    2
    3x1                    3
    2x2                    4
    3x2                    6
    3x3                    9
    4x3                   12

The user can click on a 'Launch' button to start playing the
aniGIF files, in the order of the filenames within the playlist file.

When the 'Launch' button is clicked the first N aniGIF files
are shown on the screen.

A 'Next N' button allows for displaying the next N aniGIF files.

   (Typically, one would want to close the currently-running
    N player-windows before clicking on the 'Next' button.

    However, one could simply click on the 'Next' button --- and
    then move player-windows around so that all or most of the
    player-windows are showing, without overlap.)

********
STOPPING:
********

This utility also provides for 'interrupting' the display of
the sequence of aniGIF files --- via a 'Stop' button on the GUI.

This is handy in the cases where the playlist file contains
the names of MANY aniGIF files --- and the user realizes that
he/she does not want to see them all at this time.

If the user does not use the 'Stop' button, he/she can keep
clicking on the 'Next N' button to show ALL the aniGIF files
in the playlist file. OR ... click on 'Exit' to close the GUI
and stop showing any more aniGIF files of the playlist file.

****************
PLAYER PROGRAMS:
***************

Some examples of animated-GIF 'player' programs:

   - ImageMagick 'animate'

   - 'gifview', which comes with the 'gifsicle' program that
                can be used to create animated GIF files.

The player program is chosen via radiobutton widgets on the GUI.

The code for the Tk script that constitutes this utility can be
easily changed to allow for additional or other animated-GIF players.

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

*****************
THE PLAYLIST FILE:
*****************

The intent of this utility is to allow the user to easily
specify files to be played ... by building a simple 'playlist'.

The playlist file contains three types of lines:

  1 - Comment lines may be indicated in the file by a # sign
      in column 1.

  2 - Lines that contain a fully-qualified directory name
      or a 'dot' directory name.
      Examples:  /data/aniGIFs/smileys         OR
                 \$env(HOME)/aniGIFs/smileys   OR
                 /home/fred/aniGIFs/smileys    OR
                 .                             OR
                 ./other

      These lines must start with either slash (/) or $ or dot(.).

      The dot can be used to represent the directory in which
      the playlist file lies. This useful when putting the playlist
      file in a directory of animated GIFs and using the dot to
      represent the directory of the aniGIF files. The directory of
      aniGIF files may have sub-directories of aniGIF files, as indicated
      by the last example.  The dot allows for moving/copying the
      aniGIF files (with the playlist file) to different directories
      without having to change the internals of the playlist file.

  3 - Lines that contain a relative filename (relative to the
      previous directory name).
      Example: smiley_dancing_20x20_ani.gif

      These lines SHOULD NOT start with either slash(/) or $ or dot(.)
      or # --- although a # could be used to comment out a filename.

The first non-comment line of the playlist file should be a
directory name.

There can be more than one directory name in the file.

Each directory-name-line is followed by names of aniGIF files
that are in the specified directory.


***************************************
HOW TO CHANGE Animated-GIF File PLAYERS:
***************************************

A different aniGIF player program could be used in place of any of the
current aniGIF-players by some simple label-text changes, to an 'aRtext'
array variable, in the Tk script

   playAniGIFfilesInPlaylist_FrontEnd.tk

--- and, additionally, a few coding changes to 'if' statements in the proc

   set_player_command

of that Tk script.


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

The GUI consists of about

  -  3 label widgets
  -  8 button widgets
  -  1 entry widget
  -  9 radiobutton widgets in 2 groups
  -  0 checkbutton widgets
  -  0 scale widgets
  -  0 listbox widgets

The button, entry, and radiobutton widgets are the control widgets
of the GUI --- so there are about 18 control options.


**************************
TYPICAL OPERATION SEQUENCE:
**************************

STEP 1:

You can use the 'Browse...' button to retrieve a full filename
to the playlist filename entry field.

STEP 2:

Before clicking on the 'LaunchPlayerJob' button,
you can also change *radiobutton* settings as needed --- for

- a player program

and

- layout of player windows on the screen --- 1x1(1), 2x1(2),
  3x1(3), 2x2(4), 3x2(6), 3x3(9), and 4x3(12).

*THEN* click on the 'Launch' button.

STEP 3:

Clicking on the 'Next N' button causes the next N animated GIF files
to be played.

The user has the option whether to close the currently running aniGIF
'player' windows before starting the next N windows.

STEP 4:

Keep doing 'Step 3', until all aniGIFs of the playlist are shown ---
or until the 'Stop' button or 'Exit' button is used.

---

Clicking on the 'Stop' button sets an indicator. The next time the user
clicks on the 'Next N' button, the user will be advised that no more
of the aniGIF's in the playlist file will be shown.

After a 'Stop', the user can use the 'Launch' button to start another
playing sequence via a playlist file.

---

In the future, additional options could be added to the GUI, via adding
a few widgets, to allow for

   - controlling the speed of the playback of animated GIF's

   - controlling the number of loops through the animated GIF's

   - scaling up/down the size of the playback windows

and other playback options --- if at least one of the aniGIF 'players'
supports the option.


********
CONTROLS of the Animated-GIF-Players:
********

*************************************************
FEATURES & IDIOSYNCRACIES OF THE 'animate' PLAYER:
*************************************************

The 'man' page for the 'animate' command is about 120 lines long
(about 2 pages).

There are about 70 possible COMMAND-LINE OPTIONS for 'animate', but the
main option we use is '-geometry' --- to place the aniGIF-player windows
on the screen --- for a user-selected 'layout'.

In the 'man help', there are no documented KEYBOARD CONTROLS that
you can use when the 'animate' display window comes up --- for
example, to pause-playing or to re-size the image. However ...

If you click on an 'animate' window, an ImageMagick menu GUI appears.


*************************************************
FEATURES & IDIOSYNCRACIES OF THE 'gifview' PLAYER:
*************************************************

The 'man' page for the 'gifview' command is about 150 lines long
(about 2.5 pages).

Although the 'gifview' command (which is usually packaged with
the 'gifsicle' command) does not have nearly as many COMMAND-LINE
OPTIONS as the ImageMagick 'animate' command, the 'gifview' command
may be handy to try if 'animate' fails to play an animated GIF file.

'gifview' has about a dozen COMMAND-LINE OPTIONS, but the main
two that we use are:

   -a         to animate the GIF file -- rather than showing it in a
              default 'slideshow' mode

   --geometry +x+y   to place the various windows -- for a user-selected
                     'layout' of aniGIF-player windows on the screen.

'gifview' does not have any control options in a GUI.
But there some KEYBOARD CONTROLS, including:

   q           Quit gifview.
   ESC         Stop the animation.
   Space or n  Go to the next frame.
   b or p      Go to the previous frame.
   r or <      Go to the first frame.
   >           Go to the last frame.
   s or a      Toggle between animation and slideshow mode.
   u           Toggle between normal and unoptimized mode.

---

NOTE: We must use '-geometry' for 'animate' and '--geometry' for 'gifview.


***********************************************
SETTING UP THIS UTILITY FOR EASY ICON-CLICK USE:
***********************************************

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

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 (or in a desktop 'panel').

Then, whenever the user wants to start the
'play-aniGIF-files-in-a-playlist' GUI, the user can
click on the desktop icon to startup the Tk script.


*****************
STARTUP DIRECTORY:
*****************

If you want the playlist filename to start at a
different directory, in the Tk script, you can look for
the line

    set playlistDIR \"\$env(HOME)\"

and change it according to nearby examples.
"


##+######################################################
## ADDITIONAL GUI INITIALIZATION section.
##+######################################################
## NOTE: Some of the settings of variables done when
## widgets were defined in the widget-defining section
## could be moved down here to keep most of the
## initializing of variables in one place.
##+######################################################

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


##+###########################################
## Disable/enable widgets according to initial
## GUI settings. (NOT IMPLMENTED, yet)
##+##########################################

# disable_enable_widgets


INSTALLING THE SCRIPT:

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

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 (or in a desktop 'panel').

Then, whenever the user wants to start the 'play-aniGIF-files-in-a-playlist' GUI, the user can click on the desktop icon to startup the Tk script.


SOME POSSIBLE ENHANCEMENTS

This Tk script adds another 'Front End' utility at the bottom of my 'bio' page at uniquename --- in the 'CFE' (Code for Front Ends) group.

I plan to work on a 'batch-find' script for animated-GIF files --- similar to the 'front end' scripts that I made for a combination of the 'find' command and a choice of image-viewers/media-players.

Also I plan to work on 'front ends' for 'mplayer', 'xrandr', 'ffmpeg', 'find(-and-copy/link)', and several other programs.

But I may return to this 'play-aniGIFs-in-a-playlist' Tk script to provide some enhancements.

Additional options may be added to the GUI, via adding a few widgets, to allow for

   - controlling the speed of the playback of animated GIF's

   - controlling the number of loops through the animated GIF's

   - scaling up/down the size of the playback windows

and other playback options --- if at least one of the aniGIF 'players' supports the option.

I may change the way the 'Stop' button is implemented --- to provide more immediate feedback to the user that playing the playlist has been stopped.

---

FEATURES & IDIOSYNCRACIES OF THE 'animate' PLAYER

The 'man' page for the 'animate' command is about 120 lines long (about 2 pages).

There are about 70 possible COMMAND-LINE OPTIONS for 'animate', but the main option we use is '-geometry' --- to place the aniGIF-player windows on the screen --- for a user-selected 'layout'.

In the 'man help', there are no documented KEYBOARD CONTROLS that you can use when the 'animate' display window comes up --- for example, to pause-playing or to re-size the image. However ...

If you click on an 'animate' window, an ImageMagick menu GUI appears.

---

FEATURES & IDIOSYNCRACIES OF THE 'gifview' PLAYER

The 'man' page for the 'gifview' command is about 150 lines long (about 2.5 pages).

Although the 'gifview' command (which is usually packaged with the 'gifsicle' command) does not have nearly as many COMMAND-LINE OPTIONS as the ImageMagick 'animate' command, the 'gifview' command may be handy to try if 'animate' fails to play an animated GIF file.

'gifview' has about a dozen COMMAND-LINE OPTIONS, but the main two that we use are:

   -a         to animate the GIF file -- rather than showing it in a
              default 'slideshow' mode

   --geometry +x+y   to place the various windows -- for a user-selected
                     'layout' of aniGIF-player windows on the screen.

'gifview' does not have any control options in a GUI. But there some KEYBOARD CONTROLS, including:

   q           Quit gifview.
   ESC         Stop the animation.
   Space or n  Go to the next frame.
   b or p      Go to the previous frame.
   r or <      Go to the first frame.
   >           Go to the last frame.
   s or a      Toggle between animation and slideshow mode.
   u           Toggle between normal and unoptimized mode.

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 2014apr22

In using this utility, I found a few changes were advisable:

1) Added a 'file exists' check to proc 'play_nextN_aniGIF_files' --- to handle cases when there may be non-existent filenames or filenames with syntax errors in the playlist file.

2) Added a 'RETcode' (return code) check to proc 'play_nextN_aniGIF_files' --- to handle cases where the player program cannot handle a specified file.

3) Added the capability to use a dot (.) at the start of any directory name in the playlist file --- to represent the directory in which the playlist file lies. This useful when putting the playlist file in a directory of animated GIF files and we want to use the dot to represent the directory of the aniGIFs. The dot allows for moving/copying the aniGIF files (with the playlist file) to different directories without having to change the internals of the playlist file.

4) Changed <KeyPress> to <KeyRelease> in a binding on the playlist filename entry field.

The code above has been replaced with the updated code.


uniquename UPDATE 2014may28

In using this utility some more since posting it here a couple of months ago, I found that I needed to make a couple of changes.

1) I moved 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'.

2) I added 'eval' to a 'set' statement in the proc 'show_aniGIF_filenames' and moved an 'eval' command to a 'set' statement in proc 'play_nextN_aniGIF_files'. This was to properly implement the intended feature of allowing the use of environment variables in directory names of the playlist file. Example: $env(HOME)

The code above has been replaced with the updated code.