tkShowImageFilesInPlaylist - a front-end for multiple image viewers

uniquename - 2014mar02

On 2013dec10, I contributed code at tkImageViewer - for Mask-Selecting Images from a Directory Hierarchy --- to provide a Tk GUI 'front end' to a combination of the 'find' command and a user-selectable image viewer command/program.

The GUI was designed to offer the user the ability to 'automatically' select image files within a DIRECTORY HIERARCHY --- according to criteria such as filename-mask, file-size, file-age, and file-type --- and show the image files in a sequence --- with a choice from several image viewer programs --- such as ImageMagick 'display' or 'ffplay' or 'eog' (Eye of Gnome).

(It is quite easy to switch out any of these viewer programs/commands to replace them with another image viewer that is available in a user's operating environment.)

This 'Batch-Find-and-Image-Viewer' utility was implemented as a Tk script and 2 shell scripts. As such, it should be implementable with very few changes in various Linux/BSD/Mac/Unix operating system environments.

I added the 'Tk (Batch) Image Viewer' utility to the list of 'done and to-do' utilities in the 'CFE' (Code - Front Ends) section near the bottom of my 'bio' page at uniquename.

Soon I was thinking about another 'batch viewing' utility that I would like to add to that list.

Soon after submitting (and updating/enhancing) the 'find'-and-imageViewer 'FrontEnd' utility, I realized that, with relatively few changes, the Tk script and the 2 shell scripts could be altered to provide a way to 'find' movie (and/or audio) files and play them with any of the many 'media-player' programs that are available on Linux and other such systems.

Some of the 'media-player' programs that I was aware of were:

  • mplayer
  • ffplay
  • totem
  • gnome-mplayer
  • VLC
  • smplayer
  • gmplayer

Several of these are GUI front-ends to the 'mplayer' program --- or they use media library routines that were used to implement the 'mplayer' program. At least one uses the 'Gstreamer' media library.

To make a long story short, that 'batch media-file viewer' utility was made available on 2013dec15 at tkMediaPlayer - to Batch-Select-and-Play Movie & Audio files in a Directory Hierarchy.


A NEXT STEP IN FILE VIEWING

An intended use of the 'batch-image-viewer' and 'batch-media-player' utilities was to allow for quickly surveying image/media files that may be scattered throughout a directory hierarchy --- 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.

However, two drawbacks are that (1) the files are viewed in a fixed order (according to a filename sort), and (2) the user may encounter many files that they are not so interested in seeing.

On the other hand, the 'batch-image-viewer' and 'batch-media-player' utilities have a 'show-selected-files' button-option that lists the files selected via the search criteria (filename-mask, file-size, file-age, and/or file-type). The list is shown in a popup window, in a Tk 'text' widget.

So the list of files could be pasted in a text-editor window --- and the user could change the ORDER of the files and ELIMINATE files that the user does not want to view/play, for this particular group of files.

In other words, the user could rather easily make a 'playlist' of files to be viewed with a 'light-weight' image-viewer program --- or a playlist of movie/audio files to be played with a 'media-player' program.

If I were to prepare a playlist file of images, I just needed a utility to show the files --- preferably a low-cost (like free) utility that is easily enhanced/modified/fixed.

I decided to start with a 'show-image-files-in-a-playlist' Tk-script utility. That is the subject of this page. A 'play-media-files-in-a-playlist' utility will come later.

---

VIEWERS FOR JPEG, GIF, PNG FILES

I would like to have made my own viewer, using Tcl-Tk and the Tk 'canvas' widget. I could have made a viewer that has the best features of viewers like 'display', 'ffplay', and 'eog', mentioned above --- while providing features that they lack.

Unfortunately, Tcl-Tk does not 'natively' support reading JPEG-JFIF files yet. And it requires installing Tcl-Tk 6.x to show PNG files.

I wanted a viewer that would not require adding a Tcl-Tk 'extension' --- which most potential users (like me) are not going to want to do --- especially if they discover that they also have to install a 'compatible' version of the Tcl-Tk 'wish' interpreter (which might be an older version).

Notable quotes come to mind here:

"One thing leads to another." and "Whatever can go wrong, will."

So it seemed the most desirable way to proceed (in my view) was to use some 'external, light-weight' image-viewers --- like 'display' and 'ffplay' and 'eog' --- and build a 'front-end' using Tcl-Tk.

---

THE GOALS

My goals for the Tcl-Tk script were:

  - provide a GUI for selecting a 'playlist' of image files.

  - allow the user to select an image-viewer program from among multiple choices.

  - allow the user to select a 'fullscreen'-size or 'default'-size mode of display.

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

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

  - like on the 'batch-find-and-imageView' and 'batch-find-and-mediaPlay' utilities,
    provide 'Count' and 'List' buttons on the GUI to allow for quickly counting
    and displaying the (full) filenames of the image files in the playlist file.

---

THE GUI LAYOUT

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

  ---------------------------------------------------------------------------
  Show the *Image* Files of a *Playlist* ...  with a choice of image-viewers
  [window title]
  ---------------------------------------------------------------------------

  {Exit} {Help} {LaunchViewerJob} {Stop (no more images)}  {CountImages} {ShowImgFilenames}

  Playlist Filename: ___________________________________________________  {Browse...}

  Viewer Program:  O 1 - ImageMagick 'display'    O 2 - 'ffplay'   O 3 - 'eog'

  Display Size: O FULLSCREEN (no toolbars)  O DEFAULT (image size, up to fullscreen) 

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

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

   -  7 button widgets
   -  3 label widgets
   -  1 entry widget
   -  5 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/images/vacation2014  or
                 $env(HOME)/vacation2014   or
                 /home/fred/vacation2014
      These lines must start with either slash (/) or $.

  3 - Lines that contain a relative filename (relative to the
      previous directory name)
      Example: MountShasta_800x600.jpg
      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 images playlist file:

 # A description of this file could go here.
 #
 # First directory:
 $env(HOME)/IMAGE_CAPTURE
 BernieAndSusan_atJonesWedding2007_1_264x351.jpg
 BernieAndSusan_atJonesWedding2007_1_264x351_PIXELATED.jpg
 #
 # Second directory:
 $env(HOME)/IMAGE_CAPTURE/tiles
 tile_ripple_gray_320x200.jpg

FRONT END FOR OPTIONS FOR THE VIEWERS

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

A second set of radiobuttons allow for running the several image-viewers in 'fullscreen' mode, instead of a 'default' mode. The 'default' mode may vary, depending on the viewer chosen, but it is typically show-the-image-at-its-original-size- unless-it-is-too-large-for-the-screen.

In the future, if there are some either-or options (of at least one of the image-viewers) 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 viewing images that are specified in a playlist file (with a very simple format) --- and with several different choices of viewer --- 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.

showImgFilesInPlaylist_initial_screenshot_627x147.jpg

Note that there are several radiobuttons that allow you to choose the image-viewer to use --- and a couple of radiobuttons to choose fullscreen-mode, or not.

---

TYPICAL SEQUENCE OF OPERATIONS WITH THE GUI

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

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

- a viewer program

and

- display size ('fullscreen' or the 'default-size' used by the viewer).

*NOTE*: Closing the viewer window causes the next image to be shown.

---

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 'CountImages' or 'ShowImageFilenames' 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.

NOTE that most 'simple' image-viewers, like 'eog', and even more complex image viewers, are oriented toward 'playing' through the image files in a SINGLE directory --- usually in filename order.

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 'show-image-files-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.


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_images'            - called by the 'CountImages' button.

  'show_image_filenames'    - called by the 'ShowImgFilenames' button.

  'show_images'             - called by the 'LaunchViewerJob' button.

  'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
                                    Also called via the 'CountImages' and
                                    and 'ShowImgFilenames' buttons

Like with the 'batch-find-and-imageViewer' and 'batch-find-and-mediaPlayer' 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 finding a way to get the 'Stop' button to work. At first I used the Tcl 'exec' command to start the viewer(s) in a 'foreground' mode. But this locked up the Tk GUI while an image was being shown by the 'external' viewer.

I eventually settled on using the 'exec' command to start the viewer in a 'background' mode. Example:

   catch {eval exec $VIEWERcommand "$IMGfilename" &} ViewerPID

Thus I had a PID (process ID) for the viewer process.

Then I used a 'while' loop, including an 'after 200' statement to occasionally check, with the 'ps' command, whether the process was still running --- thanks to a code snippet seen at http://stackoverflow.com/questions/16012040/how-do-i-check-for-spawn-id-thats-alive-tcl .

From that example, I devised the following simple loop

   while {1} {
      update
      after 200
      if {[catch {exec ps $ViewerPID} CatchMsg] != 0} {break} 
   }

The 'update' command is to allow for checking whether the 'Stop' button has been pressed --- as the 'show_images' proc is proceeding to show the next image. (The Stop button sets a stop-variable to 1 rather than 0.)

Only the one 'catch-exec-ps-break' line was needed to determine whether the user had closed the image viewer window and it was now time to show the next image (and check whether the Stop button had been pressed).


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

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of 'liquid mud' flowing down the hillsides of Southern California during the recent (2014mar02) rainstorms pounding a drought-ravaged California.


 Code for Tk script 'showImgFilesInPlaylist_FrontEnd.tk' :
#!/usr/bin/wish -f
##
## Tk SCRIPT NAME: showImgFilesInPlaylist_FrontEnd.tk
##
##+#######################################################################
## PURPOSE:  This Tk script provides a GUI for selecting a 'playlist' of
##           image files. The image 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.
##
##           Besides selecting the playlist file, this utility
##           allows the user to select an image-viewer program from among
##           multiple choices --- and allows the user to specify a
##           fullscreen-size or default-size display mode.
##
##           Then the user can click on a 'Launch' button to start showing the
##           images, in the order of the filenames within the playlist file.
##
##           Some examples of the image-viewer program:
##                - the ImageMagick 'display' program
##                - the 'ffplay' program
##                - the 'eog' (Eye of Gnome) program.
##           The viewer program is chosen via radiobutton widgets on the GUI.
##           This code can be changed to allow for additional or other
##           image viewers.
##
##           The GUI is intended to make the user aware of the main
##           (most useful) options available without the user needing
##           to reference the 'man display'/'man ffplay'/'man eog' commands
##           and other 'display'/'ffplay'/'eog' documentation, for example,
##           via web searches.
##
##           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 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/images/vacation2014 or
##                           $env(HOME)/vacation2014  or
##                           /home/fred/vacation2014  or
##                           .                        or
##                           ./camping
##                 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 images and using the dot to
##                 represent the directory of the images. The directory of
##                 images may have sub-directories of images, as indicated
##                 by the last example. The dot allows for moving/copying the
##                 images (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: MountShasta_800x600.jpg
##                 These lines DO NOT start with either slash (/) or $ or
##                 dot 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.
##
## NOTEWORTHY FEATURE OF THIS UTILITY:
##
##           NOTE that most image-viewers, like 'eog' and more complex
##           image viewers, are oriented toward 'playing' through the
##           image files in a SINGLE directory --- usually in filename order.
##
##           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). 
##            
##+######################
## NOTES ON OTHER VIEWERS:
##           Other 'simple' image viewer programs that might be useful
##           are 'feh', 'gwenview', 'gpicview', ImageMagick 'animate',
##           "Eye of MATE', and quite a few others.
##
##           As an alternative to 'eog', it would be easy to use the
##           'Eye of MATE' image viewer instead. Let us abbreviate the
##           viewer name as 'eom'. 'eom' is the image viewer that the
##           MATE desktop project forked from 'eog'.
##           Reference: www.mate-desktop.org
##
##           Circa 2010, some Argentinians decided to fork the
##           Gnome 2.x desktop environment --- when the Gnome 3 project
##           abandoned many of the operational features of the Gnome 2 desktop
##           environment and its associated applications --- applications like
##           the 'Nautilus' file manager and the 'Eye of Gnome' image viewer.
##           (The fork of the 'Nautilus' file manager is called 'Caja' ---
##            Spanish for 'box' or 'gift'.)
##
##           Of course, still other 'light-weight' image viewers could be
##           used with very few changes to this system. The 'core' changes
##           would be to a proc within this script --- 'play_images'.
##           That proc currently uses 'display' or 'ffplay' or 'eog',
##           along with other (viewing) parameters that can be supplied
##           via this GUI.
##
##+#################
## THE GUI WIDGETS:
##
##           The options available to the user are compactly indicated
##           by the following 'sketch' of the GUI:
##
##  ---------------------------------------------------------------------------
##  Show the *Image* Files of a *Playlist* ...  with a choice of image-viewers
##  [window title]
##  ---------------------------------------------------------------------------
##  
##  {Exit} {Help} {LaunchViewerJob} {Stop (no more images)}  {CountImages} {ShowImgFilenames}
##  
##  Playlist Filename: ___________________________________________________  {Browse...}
##  
##  Viewer Program:  O 1 - ImageMagick 'display'    O 2 - 'ffplay'   O 3 - 'eog'
##  
##  Display Size: O FULLSCREEN (no toolbars)  O DEFAULT (image size, up to fullscreen) 
##
## --------------------------------------------------------
##
## 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.
##
##+##############
## GUI components:
##
## From the GUI 'sketch' above, it is seen that the GUI consists of
## about
##     
##   -  7 button widgets
##   -  3 label widgets
##   -  1 entry widget
##   -  5 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/tkShowImgsOfPlaylist.
##
##             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, widget-geom-parms, win-size-control, text-array-for-labels-etc).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack the frames.
##
##  2) Define & pack all widgets in the frames, frame by frame.
##     After all the widgets for a frame are defined, pack them in the frame.
##
##  3) Define keyboard and/or mouse/touchpad/touch-sensitive-screen 'event'
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically with one or more of
##     the procs), if needed.
##
## In more detail:
##
##  1a) Define ALL frames -- and sub-frames:
## 
##   Top-level :
##       'fRbuttons'     for Exit, Help, Launch, ... buttons
##       'fRfile'        for a playlist (directory-and-filename) entry field
##       'fRdisplayPgm'  for several radiobuttons, with a label
##       'fRdisplaySize' for 2 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
##
##    'count_images'          - called by the 'CountImages' button.
##
##    'show_image_filenames'  - called by the 'ShowImgFilenames' button.
##
##    'play_images'           - called by the 'LaunchViewerJob' button.              
##
##   'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
##                                         Also used via the 'CountImages'
##                                         and 'ShowImgFilenames' 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 2014feb26 Started development, on Ubuntu 9.10,
##                                        based on the code of some Tk scripts
##                                        of mine that contained most of the
##                                        widgets needed.
## Updated by: Blaise Montandon 2014mar01 Implement the procs --- 'count_images',
##                                        'show_image_filenames', 'show_images'.
## Updated by: Blaise Montandon 2014apr14 Added 'file exists' and RETcode checks
##                                        in proc 'play_images'.
## Updated by: Blaise Montandon 2014apr21 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 3 close-file statements.
## Updated by: Blaise Montandon 2014may26 0) Changed the name of proc 'show_images'
##                                           to 'play_images'.
##                                        1) Replaced var 'fileID' occurences by
##                                           'fileIDcount','fileIDshow','fileIDplay'.
##                                        2) Added 'eval' to a 'set' statement in proc
##                                           'show_image_filenames' and to a 'set'
##                                           statement in proc 'play_images'.
##+#######################################################################

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

wm title    . "Show *Image* Files of a *Playlist* ... with a choice of viewers"

wm iconname . "tkImgPlaylist"

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


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

tk_setPalette "#e0e0e0"


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


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

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

font create fontTEMP_SMALL_varwidth \
   -family {comic sans ms} \
   -size -12 \
   -weight bold \
   -slant roman

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


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

font create fontTEMP_SMALL_fixedwidth  \
   -family {liberation mono} \
   -size -12 \
   -weight bold \
   -slant roman

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


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

## LABEL widget geom settings:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2


## BUTTON widget geom settings:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## ENTRY widget geom settings:

set BDwidthPx_entry 2


## RADIOBUTTON widget geom settings:

set PADXpx_radbutton 0
set PADYpx_radbutton 0
set BDwidthPx_radbutt 2


## CHECKBUTTON widget geom settings:

set PADXpx_chkbutton 0
set PADYpx_chkbutton 0
set BDwidthPx_chkbutton 2

## TEXT widget geom settings:

set BDwidthPx_text 2


## 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)  "LaunchViewerJob"
set aRtext(buttonSTOP1)   "Stop (no more images)"
set aRtext(buttonSTOP2)   "Stop indicator was SET"

set aRtext(buttonCOUNT)  "CountImages"
set aRtext(buttonPRINT)  "ShowImgFilenames"

## For '.fRfile' frame:

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


## For '.fRdisplayPgm' frame:

set aRtext(labelDISPLAYPGM)   "Viewer Program:"
set aRtext(radbuttDPGM1) "1 - ImageMagick 'display'"
set aRtext(radbuttDPGM2) "2 - 'ffplay'"
set aRtext(radbuttDPGM3) "3 - 'eog'"

## For '.fRdisplaySize' frame:

set aRtext(labelDISPLAYSIZE)  "Display size:"
set aRtext(radbuttDSIZEFULL)    "FULLSCREEN (no toolbars)"
set aRtext(radbuttDSIZEDEFAULT) "DEFAULT (image size, up to fullscreen)"

## 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 '.fRdisplaySize' 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(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 '.fRdisplaySize' 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 4 pixels/widget = 16 pixels).

set minWinHeightPx [expr {36 + $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
##       'fRdisplaySize' for 2 radiobuttons, with a label
##+####################################################################
##+####################################################################

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

# set feRELIEF_frame raised
# set feBDwidth_frame 2

 set feRELIEF_frame flat
 set feBDwidth_frame 0

frame .fRbuttons     -relief $feRELIEF_frame  -bd $feBDwidth_frame

frame .fRfile        -relief raised           -bd 2

frame .fRdisplayPgm  -relief raised           -bd 2

  frame .fRdisplaySize  -relief $feRELIEF_frame  -bd $feBDwidth_frame
# frame .fRdisplaySize  -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 .fRdisplaySize \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0


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

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

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

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

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

set STOPvar0or1 0

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

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

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

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

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttLAUNCH \
     .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 \
   -relief  flat \
   -bd $BDwidthPx_label

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

## Set an initial value for the entry var.

set curDIR "$env(HOME)"
# set curDIR "$env(HOME)/MyImgPlaylists"
# set curDIR "/data/playlists"

## FOR TESTING:
   set curDIR [pwd]

set ENTRYfilename "$curDIR/images_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 \
   -relief raised \
   -bd $BDwidthPx_button \
   -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
## severak RADIOBUTTONS, preceded by a LABEL widget.
##+########################################################

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

## DEFINE Radiobuttons for viewer programs :

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

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

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

## 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 \
      .fRdisplayPgm.radbuttDPGM3 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 



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


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

## DEFINE Radiobuttons for display sizes :

radiobutton  .fRdisplaySize.radbuttDSIZEDEFAULT \
   -text "$aRtext(radbuttDSIZEDEFAULT)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARdisplaysize \
   -value "default" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt

radiobutton  .fRdisplaySize.radbuttDSIZEFULL \
   -text "$aRtext(radbuttDSIZEFULL)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable RADVARdisplaysize \
   -value "fullscreen" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -bd $BDwidthPx_radbutt


## RADVARdisplaysize is the var for these 2 radiobuttons.
## Set an initial value.
   set RADVARdisplaysize "default"
#  set RADVARdisplaysize "fullscreen"

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

pack  .fRdisplaySize.labelDISPLAYSIZE \
      .fRdisplaySize.radbuttDSIZEDEFAULT \
      .fRdisplaySize.radbuttDSIZEFULL \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 


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

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

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

## As the user types in the 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_images'            - called by the 'CountImages' button.
##
##  'show_image_filenames'    - called by the 'ShowImgFilenames' button.
##
##  'play_images'             - called by the 'LaunchViewerJob' button.
##
##  'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var.
##                                    Also called via the 'CountImages' and
##                                    and 'ShowImgFilenames' 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 curDIR

   #############################################
   ## Get an 'image-playlist' file name.
   #############################################

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

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

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

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

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

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

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

      set curDIR [ 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 'images' 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 'images' 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_images
##
## PURPOSE: For a given playlist file, counts the
##          image filenames in the playlist file ---
##          by counting the non-empty lines that do NOT
##          start with slash (/) or $ or #.
##
## CALLED BY: the 'CountImages'  button
##+#############################################################

proc count_images {} {

   global ENTRYfilename env

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

   #######################################################
   ## 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 image files in the playlist file.
   ###############################################################

   set fileIDcount [open $ENTRYfilename]

   set Nimages 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 tempDIR var and) read next line.
      #################################################################

      if { "$FIRSTchar" == "/" || "$FIRSTchar" == "$" || \
           "$FIRSTchar" == "." } {
         # set tempDIR "[string trim $line]"
         continue
      }

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

      set EMPTYLINEcheck "[string trim $line]"

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

      incr Nimages

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

   catch {close $fileIDcount}

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

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

}
## END OF proc 'count_images'


##+#############################################################
## proc show_image_filenames
##
## PURPOSE: For a given playlist file, shows the
##          image filenames in the playlist file ---
##          in a Tk popup window with a scrollable text widget.
##
## CALLED BY: the 'ShowImgFilenames'  button
##+#############################################################

proc show_image_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 IMAGE FILENAMES of
   ## the 'playlist file'.
   ###############################################################
   ## Prepare for getting image filenames in the playlist file.
   ###############################################################

   set fileIDshow [open "$ENTRYfilename"]

   set Nimages 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 'tempDIR' VAR and read next line.
      #################################################################

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

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

      if { "$FIRSTchar" == "."  } {
         set lengthOFline [string length "$line"]
         if {$lengthOFline > 1} {
            set restOFline [string range "$line" 1 $lengthOFline]
            set tempDIR "${thisDIR}$restOFline"
         } else {
            set tempDIR "$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 IMAGEfilename "$tempDIR/$EMPTYLINEcheck"

      set TEXTfilenames "$TEXTfilenames
$IMAGEfilename"

      incr Nimages

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

   catch {close $fileIDshow}


   ###############################################################
   ## DISPLAY THE IMAGE 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 $Nimages full-filenames of the image files in the playlist file 
are as follows:

$TEXTfilenames"

}
## END OF proc 'show_image_filenames'
 

##+#############################################################
## proc play_images
##
## PURPOSE: For a given playlist file, shows the images indicated
##          by directory names and 'relative' image filenames in
##          the playlist file.
##
##          Uses the display program indicated by the variable
##          $RADVARdisplaypgm.
##
##          Uses other variables, such as $RADVARdisplaysize,
##          to determine options for showing the images.
##
## CALLED BY: the 'LaunchViewerJob'  button
##+#############################################################

proc play_images {} {

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

   global ENTRYfilename env RADVARdisplaypgm RADVARdisplaysize \
      STOPvar0or1 aRtext

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

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


   ###############################################################
   ## Logic goes here to read the playlist file and
   ## SHOW THE IMAGES.
   ###############################################################
   ## Prepare for showing image files in the playlist file ...
   ## mainly, open the file and set a viewer command.
   ###############################################################

   set fileIDplay [open "$ENTRYfilename"]

   set Nimages 0

   ###################################################################
   ## Some 'display' Application Options:
   ##          -geometry geometry   preferred size and location of the Image window 
   ##          -resize geometry     resize the image
   ##
   ##          -backdrop                  display image centered on a backdrop
   ##                          (Note: This option does not play nicely.
   ##                           The 'display' process does not end when
   ##                           the user closes the window. So I do not
   ##                           use '-backdrop'. It seems to have been 
   ##                           tested mostly for putting an image on
   ##                           the root window.)
   ##
   ##          -page geometry       size and location of an image canvas
   ##     -repage geometry     size and location of an image canvas (operator)
   ## The '-page' and '-repage' options are for establishing a 'virtual
   ## canvas' within the display window. The 'virtual canvas' can
   ## exceed the size of the window. I tried the 'page' options to force
   ## the 'display' window into a 'fullscreen' size. No luck.
   ##
   #########################################################################
   ## At http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=23110,
   ## an ImageMagick expert, 'anthony', admits:
   ##
   ## "I would have thought that a '-geometry' setting to 'display' would
   ## override 'size to fit image'... it flashes to full screen but then its
   ## window is sized to fit image! I am not certain if I would consider this
   ## a bug or a feature, but without some means of telling display NOT to
   ## auto-size (or perhaps to never auto-shrink) the window, I would say
   ## that it is a bug."
   ##
   ## It seems the best we can do is use the '-resize' option to scale up
   ## the image and force the 'display' window to be larger.
   ##
   ## Or, as the discussion suggests, try another image viewer such as 'feh'.
   ##################################################################
   ## At http://www.imagemagick.org/Usage/basics/#display ,
   ## is found a quote with an example:
   ##
   ## "... the following is my recommendation for using 'display' to
   ## display results from a complex shell script...
   ##
   ##  display -delay 0 -loop 1 -coalesce -resize 800x600\>  some_random_image"
   ##
   ## The '-resize' parameter limits display to, say, a 800x600
   ## pixel area ... that is, only resize smaller, never larger.
   ##    The '-coalesce' parameter helps when displaying images
   ##    within an animated GIF file.
   ##
   ## Also from that web page is:
   ## "If you don't want menus, you can turn them off using the
   ## '-immutable' setting to 'display', so it knows not to allow editing."
   ##
   ## It may be good to follow these suggestions and use the
   ## '-delay 0' and '-loop 1' and '-immutable' parameters.
   #######################################################################
   ## NOTE: In ImageMagick programs (after version 6),
   ## the order of the parameters makes a difference. This allows the user
   ## more possibilites of getting a set of parameters to work the way
   ## the user would like them to work --- rather than any given set of
   ## parameter values being applied in a (perhaps undocumented) order
   ## to achieve a goal that the ImageMagick programmer was aiming for.
   ###################################################################
   ## NOTE1: I set the '-resize' x,y parameters to be about 90% of the
   ##        of the screensize, because the window manager will typically
   ##        set the display window somewhat smaller than the full
   ##        screensize, to accomodate desktop panels at the bottom
   ##        and/or top of the screen.
   ##        Also, if the image exceeds the desktop display area,
   ##        the 'display' program pops up a little 'thumbnail' of
   ##        the image in which one can scroll the image. I wanted
   ##        to avoid that popup, which makes another window to close
   ##        and just makes operations more confusing than they already are.
   ## NOTE2: I am avoiding using '-window root', in case that would
   ##        mess up the user's desktop environment. I have had bad
   ##        experiences with putting images on the root window.
   ###################################################################

   if {"$RADVARdisplaypgm" == "1"} {
      if {"$RADVARdisplaysize" == "fullscreen"} {
         ## Get screen size.
         set SCREENwidthPx  [winfo screenwidth .]
         set SCREENheightPx [winfo screenheight .]
         ## Set parms for '-resize'.
         set MAXwidthPx  [expr {int(0.90 * $SCREENwidthPx)}]
         set MAXheightPx [expr {int(0.90 * $SCREENheightPx)}]
         ## Set a 'fullscreen' version of the 'display' command.
         set VARcommand "/usr/bin/display -immutable -delay 0 -loop 1 \
-geometry +15+30 \
-resize ${MAXwidthPx}x${MAXheightPx}"

         ## Options to try if '-geometry' ever works the way it should:
         ##    -geometry ${SCREENwidthPx}x$SCREENheightPx+0+0
         ##    -background black
         ##    -gravity center
         ## and maybe something like
         ##    -page ${MAXwidthPx}x${MAXheightPx}+0+0
         ## in an 'appropriate' order.
         ##    '-gravity center' is intended to place the image in
         ##     the middle of the 'fullscreen' window.

      } else {
         ## Set a 'default' version of the 'display' command.
         set VARcommand "/usr/bin/display -geometry +15+30"
      }
   }
 
   ###################################################################
   ## 'ffplay' Application Options:
   ##       -x width   Force displayed width.
   ##       -y height  Force displayed height.
   ##       -s size           Set frame size (WxH or abbreviation). Needed for
   ##                  videos which don't contain a header with the
   ##                  frame size, like raw YUV.
   ##       -an        Disable audio.
   ##       -vn        Disable video.
   ##  While playing:
   ##       q or ESC   Quit.
   ##       f          Toggle full screen.
   ## NOTE: 'ffplay' nicely centers the image within the width
   ##       and height area requested by the '-x' and '-y' parms
   ##       of the 'fullscreen' mode.
   ##       In the 'default' (non-fullscreen) mode, the 'ffplay'
   ##       window seems to always show at the top-left of the desktop.
   ###################################################################

   if {"$RADVARdisplaypgm" == "2"} {
      if {"$RADVARdisplaysize" == "fullscreen"} {
         ## Get screen size.
         set SCREENwidthPx  [winfo screenwidth .]
         set SCREENheightPx [winfo screenheight .]
         ## Set parms for '-resize'.
         set MAXwidthPx  [expr {int(0.90 * $SCREENwidthPx)}]
         set MAXheightPx [expr {int(0.90 * $SCREENheightPx)}]
         ## Set a 'fullscreen' version of the 'ffplay' command.
         set VARcommand "ffplay -an -x $SCREENwidthPx -y $SCREENheightPx "
      } else {
         ## Set a 'default' version of the 'ffplay' command.
         set VARcommand "ffplay -an "
      }
   }

   ###################################################################
   ## 'eog' Application Options:
   ##  -f, --fullscreen                   Open in fullscreen mode
   ##  -c, --disable-image-collection     Disable image collection
   ##  -s, --slide-show                   Open in slide show mode
   ##  -n, --new-instance   Start a new instance instead of reusing an existing one
   ## NOTE: 'eog' nicely centers the image within the fullscreen
   ##       area requested ... and sets the background border in the
   ##       'eog' window to black. The Ctl-W keys can be used to
   ##       close the fullscreen 'eog' window.
   ##       In the 'default' (non-fullscreen) mode, the 'eog'
   ##       window seems to show in the middle of the desktop.
   ####################################################################

   if {"$RADVARdisplaypgm" == "3"} {
      if {"$RADVARdisplaysize" == "fullscreen"} {
         ## Set a 'fullscreen' version of the 'eog' command.
         set VARcommand "eog -f -c -n "
      } else {
         ## Set a 'default' version of the 'eog' command.
         set VARcommand "eog -c -n "
      }
   }


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

   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 tempDIR VAR and read next line.
      #################################################################

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

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

      if { "$FIRSTchar" == "."  } {
         set lengthOFline [string length "$line"]
         if {$lengthOFline > 1} {
            set restOFline [string range "$line" 1 $lengthOFline]
            set tempDIR "${thisDIR}$restOFline"
         } else {
            set tempDIR "$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 image filename.
      #################################################################

      set EMPTYLINEcheck "[string trim $line]"

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

      ##################################################################
      ## Set the fully-qualified name of the image 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 IMAGEfilename "$tempDIR/$EMPTYLINEcheck"

      ## FOR TESTING:
      #   puts "IMAGEfilename: $IMAGEfilename"

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

      #########################################################################
      ## We use Nimages to keep track of how many images were shown,
      ## for a popup msg at the end-of-file.
      #########################################################################

      incr Nimages

      ###################################################################
      ## At this point the relative-filename line appears to be OK, so we
      ## SHOW THE IMAGE FILE.
      ####################################################################
      ## SOME SYNTAX NOTES on running the viewer 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 viewer program in 'foreground' mode
      ## like this makes the button widgets on the GUI unavailable ---
      ## in particular, the 'Stop' button ... and the 'Help' button.
      #######################################################################
      ## 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 "$IMAGEfilename"} CatchMsg

      ## FOR TESTING:
      #   puts "VARcommand: $VARcommand"
      #   puts "CatchMsg: $CatchMsg"

      ###############################################################
      ## A 'background' run per page 105.
      ## (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 viewer command.
      ###############################################################

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

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

RETcode: $RETcode
Message: $ViewerPID
Stopping processing."
         popup_msgVarWithScroll .topErr "$ERRtext"
         return
      }


      ## FOR TESTING:
      if {0} {
         puts ""
         puts "***********************"
         puts "VARcommand: $VARcommand"
         puts "ViewerPID: $ViewerPID"
         puts "tempDIR (directory)      :    $tempDIR"
         puts "EMPTYLINEcheck (filename): $EMPTYLINEcheck"
         puts "ViewerPID: $ViewerPID"
         puts "***********************"
         puts ""
      }


      ####################################################
      ## About every 200 millisecs, use 'update' to check
      ## the GUI for any events --- esp. a click on the 'Stop'
      ## button --- or a click on the 'Help' button ---
      ## and break out of this loop if the PID no longer exists
      ## (i.e. if the user has closed the viewer window).
      ##
      ## After we break out of this PID-check loop, we check if the
      ## 'Stop' button has been clicked (via whether its variable
      ## STOPvar0or1 is set to 1). If not, then we go on to show
      ## the next image, if any.
      ####################################################
      ## Reference: web page
      ## http://stackoverflow.com/questions/16012040/how-do-i-check-for-spawn-id-thats-alive-tcl
      ## which showed sample code using the Linux/Mac 'ps' command
      ## to check if a process with process-ID $pid is still running:
      ##
      ##   if {[catch {exec ps $pid} std_out] == 0} { 
      ##      puts "Alive"
      ##   } else {
      ##      puts "It's dead, Jim"
      ##   }
      ##############################################################
 
      while {1} {
         update
         after 200
         if {[catch {exec ps $ViewerPID} CatchMsg] != 0} {break} 
      }
      ## END OF 'while {1}' LOOP

      ############################################################
      ## CHECK TO SEE IF THE DISPLAY SEQUENCE SHOULD STOP.  I.e.,
      ## break out of the image-file-reading 'while' loop,
      ## if STOPvar0or1 is set to 1.
      ############################################################

      if { $STOPvar0or1 == 1 } {break}

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

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

   catch {close $fileIDplay}

   #########################################################
   ## In case the 'stop' button was used,
   ## reset the 'stop' button message, to indicate the 'stop'
   ## button is ready to handle another display sequence.
   ########################################################

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

   ###############################################################
   ## DISPLAY THE COUNT OF IMAGES-SHOWN 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 Nimages were greater than
   ##    25, say. That is, replace '1' by  '$Nimages > 25'.)
   ###############################################################

   if {1} {
      popup_msgVarWithScroll .topCount \
         "$Nimages images were shown --- from the playlist file ---
[get_chars_after_last "/" in "$ENTRYfilename"]
in directory
[get_chars_before_last "/" in "$ENTRYfilename"]"
   }
   ## END OF 'if {1}'

}
## END OF proc 'play_images'


##+########################################################################
## 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 utility to 'Show Images of a Playlist File' **

   ** essentially a 'front end' for an image viewer program **

This utility provides a GUI for selecting a 'playlist' of
image files. The image 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.

Besides selecting the playlist file, this utility allows
the user to select an image-viewer program from among
multiple choices --- and allows the user to select a
'fullscreen'-size or 'default'-size mode.

Then the user can click on a 'Launch' button to start showing the
images, in the order of the filenames within the playlist file.

This utility also provides for 'interrupting' the display of
the sequence of images --- via a 'Stop' button on the GUI.
This is handy in the cases where the playlist file contains
the names of MANY image files.

Some examples of the image-viewer program:
     - the ImageMagick 'display' program
     - the 'ffplay' program
     - the 'eog' (Eye of Gnome) program.
The viewer program is chosen via radiobutton widgets on the GUI.

The code for the Tk script that constitutes this utility can be
esaily changed to allow for additional or other image viewers.
Other 'lightweight' image viewer programs that might be useful
are 'feh', 'gwenview', 'gpicview', ImageMagick 'animate',
'Eye of MATE' and quite a few others.

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

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 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/images/vacation2014  or
                 \$env(HOME)/vacation2014   or
                 /home/fred/vacation2014    or
                 .                          or
                 ./camping
      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 images and using the dot to
      represent the directory of the images. The directory of
      images may have sub-directories of images, as indicated
      by the last example.  The dot allows for moving/copying the
      images (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: MountShasta_800x600.jpg
      These lines DO NOT start with either slash (/) or $ or dot 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.


************************************************
BREADTH AND FLEXIBILITY FEATURES OF THIS UTILITY:
************************************************

NOTE that most image-viewers, like 'eog' and more complex
image viewers, are oriented toward 'playing' through the
image files in a SINGLE directory --- usually in filename order.

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


***************************
HOW TO CHANGE IMAGE VIEWERS:
***************************

A different image viewer program could be used in place of 'display'
or 'ffplay' or 'eog' by some simple label-text changes in the Tk script
--- 'showImgFilesInPlaylist_FrontEnd.tk' --- and, additionally, a few
coding changes in the proc 'play_images' of that Tk script.


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

The GUI consists of about

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

The button, entry, and radiobutton widgets are the controls
on the GUI --- so there are about 13 control options.


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

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

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

- a viewer program

and

- display size ('fullscreen' or the 'default' size used by the viewer).

*NOTE*:
Closing the viewer window causes the next image to be shown.

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

   - auto-image-advance (with a 'fixed' user-specified wait time)

   - user specification of the location of the upper-left-corner of
     the view-window on the 'desktop'

   - 'stepping back' to previously shown images (There are many
      ways that this could be implemented --- some using much
      more computer memory than others.)

This would all be much easier-or-cleaner to do if Tcl-Tk would
support the reading of JPEG/JFIF files onto a Tk 'canvas' widget
--- without having to add a Tcl-Tk 'extension' --- which most
potential users are not going to want to do --- especially if
they discover that they also have to intall a 'compatible'
version of the Tcl-Tk 'wish' interpreter (which might be an
older version).
     (\"One thing leads to another.\" and
      \"Whatever can go wrong, will.\")

Not only would these options be easier-or-cleaner to implement,
they could be implemented in a better way --- avoiding the
'idiosyncracies' of 'external', compiled viewers like 'display'
and 'ffplay' and 'eog'. Some idiosyncracies are described below.


************************
FILE-TYPE CONSIDERATIONS:
************************

Note that this utility will show whatever image file types
are supported by the image viewer that is chosen.

Most image viewers support the 3 common types of image files ---
JPEG, GIF, and PNG.

The Linux/Unix 'file' command returns text strings like the following
--- on those 3 types of image files:

   - JPEG image data, JFIF standard 1.01
   - GIF image data, version 89a, 256 x 352
   - PNG image, 1024 x 768, 8-bit/color RGB, non-interlaced


*************************************************
FEATURES & IDIOSYNCRACIES OF THE 'display' VIEWER:
*************************************************

Although the ImageMagick 'display' command has many parameter options,
it does not seem to have the capability to do a nice 'fullscreen'
display of a set of images --- as revealed at
http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=23110.
It always wants to 'auto-shrink' the window around each image.

To get 'display' windows that approach fullscreen-size,
we use the '-resize' parameter to scale up the images to
near the top-bottom or left-right borders of the screen.

************************************************
FEATURES & IDIOSYNCRACIES OF THE 'ffplay' VIEWER:
************************************************

Although the 'ffplay' command does not have nearly as many parameter
options as the 'display' command, the 'ffplay' command may be
much nicer to use for 'fullscreen' mode.

'ffplay' automatically expands the images to fit in the
'ffplay' window when it is 'fullscreen'. However, for small images,
the scale-up may result in crude-looking images.

*********************************************
FEATURES & IDIOSYNCRACIES OF THE 'eog' VIEWER:
*********************************************

In 'fullscreen' mode, the user may find it easier to use
Ctrl-w to close each 'eog' window. Otherwise, it takes
one click to reduce the 'eog' window from fullscreen size,
and another click to close the down-sized 'eog' window.

'eog' has a tool icon (a magnifying-glass with a '+' in it)
that can be used to enlarge an image to fit the 'eog' window
when it is 'fullscreen'.


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

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

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


*****************
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 curDIR \"\$env(HOME)\"

and change it according to nearby examples.
"


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

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

# disable_enable_widgets


INSTALLING THE SCRIPT:

To implement the 'batch-find-and-imageView' and 'batch-find-and-mediaPlay' utilities at

tkImageViewer - for Mask-Selecting Images from a Directory Hierarchy

and

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

I used two auxiliary shell scripts, in addition to a Tk script --- that is, 3 scripts for each of those 2 utilities.

For this 'show-image-files-of-a-playlist' utility, there is a SINGLE script that can be put in a sub-directory of the user's home directory, such as $HOME/apps/tkShowImgsInPlaylist.

Then the user can use their desktop system (such as Gnome or KDE) to set up the Tk script as an icon on the desktop. Then whenever the user wants to 'show-image-files-of-a-playlist', he/she can click on the icon to startup the 'front end'.


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 similar script to 'play-media-files-in-a-playlist', for movie and audio files --- offering a choice of about 5 different mediaplayers.

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

But I may return to this 'show-images-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

1) auto-image-advance (with a 'fixed' user-specified wait time)

2) user specification of the location of the upper-left-corner of the view-window on the 'desktop'

3) 'stepping back' to previously shown images (There are many ways that this could be implemented --- some using much more computer memory than others --- for ALL the filenames in the playlist file, or for 10 filenames at a time, say.)

---

THE BIG ENHANCEMENT - A 'NATIVE' TK IMAGE VIEWER

As I mentioned above, I would actually like to make an image viewer using Tcl-Tk and the Tk 'canvas' --- when there is a 'wish' interpreter that includes JPEG-JFIF-read support.

Then I could make a viewer that combines many of the best features of image viewers like 'display' and 'ffplay' and 'eog' --- and add features that are missing from those viewers.

Until that day comes, I guess I will have to live with some of the 'quirks' of the 'display' and 'ffplay' and 'eog' viewers --- some of which are mentioned below.

   Actually, within the next year, I may go ahead and make a Tk image viewer
   that provides many of the features of image viewers like 'display' and
   'ffplay' and 'eog'. I would implement the image viewer to display GIF
   files --- and, 'underneath the covers', I would use an image conversion
   utility like ImageMagick 'convert' to convert JPEG and PNG files to
   GIF files. Thus the viewer could be used with Tcl-Tk 5.x and not
   require the user to have Tcl-Tk 6.x for PNG files. (Most operating
   systems that provide Tcl-Tk, in the 2013-2014 time frame, are still
   providing 5.x rather than 6.x.)

   The quality of display of JPEG and PNG files would suffer in many
   cases (when there are much more than 256 color shades in the file).
   This would usually be seen as 'color banding' where no such banding
   was in the original JPEG or PNG file. But having such a Tk script
   would make it easy to convert it to handle JPEG and PNG files,
   if/when there is a 'wish' interpreter that supports both
   JPEG-JFIF-read and PNG-read. 

---

FEATURES & IDIOSYNCRACIES OF THE 'display' VIEWER

Although the ImageMagick 'display' command has many parameter options, it does not seem to have the capability to do a nice 'fullscreen' display of a set of images --- as revealed at http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=23110 . 'display' always wants to 'auto-shrink' the window around each image.

To get 'display' windows that approach fullscreen-size, I used the '-resize' parameter to scale up the images to near the top-bottom or left-right borders of the screen. However, for small images, the scale-up may result in crude-looking images.

---

FEATURES & IDIOSYNCRACIES OF THE 'ffplay' VIEWER

Although the 'ffplay' command does not have nearly as many parameter options as the 'display' command, the 'ffplay' command may be much nicer to use for 'fullscreen' mode. In a 'near-fullscreen-mode', the 'ffplay' command covers almost the entire screen with a black background.

'ffplay' automatically expands the images to fit in the 'ffplay' window when it is 'fullscreen'. This can be nice for medium to large sized images. However, for small images, the scale-up may result in crude-looking images.

---

FEATURES & IDIOSYNCRACIES OF THE 'eog' VIEWER

In 'fullscreen' mode, the user may find it easier to use Ctrl-w to close each 'eog' window. Otherwise, it takes one click to reduce the 'eog' window from fullscreen size, and another click to close the down-sized 'eog' window.

'eog' has a tool icon (a magnifying-glass with a '+' in it) that can be used to enlarge an image to fit the 'eog' window when it is 'fullscreen'.


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 'show_images' --- 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 'show_images' --- to handle cases where the display program cannot handle a specified image 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 images and we want to use the dot to represent the directory of the images. The dot allows for moving/copying the images (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 I posted it a few months ago, I found a couple of changes were needed. Namely:

I added 'eval' to a 'set' statement in the proc 'show_image_filenames', and I moved an 'eval' command to a 'set' statement in the proc 'play_images'. 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)

(I also changed the name of the proc 'show_images' to 'play_images'.)

The code above has been replaced with the updated code.