tkAdsAdder - to enter TV ad start and end times, and grand total

uniquename - 2014feb06

In the past year (2013), I have noticed that U.S. TV programming seems to be adding more and more ads to almost all programs (except PBS).

In early 2013, it seemed to be the case that if I flipped through 10 channels, about 3 of 10 were showing advertising. Now, in early 2014, it seems that about 7 of 10 are showing ads.

I have become conditioned to grab the remote control and start 'channel surfing', whenever I hear the words "Stay with us" or "Stay tuned" or "We'll be right back" or "Don't go away".

The talking-heads don't seem to say "And now a word from our sponsors" anymore. They probably don't want to remind us of how much advertising they are going to dump onto us.

Nowadays, 'commercial segments' in a TV program are typically composed of about 4 to 12 individual ads.

The typical 'ads segment' is often split into TWO 'psuedo-segments' by an annuouncer saying something like 'Closed captioning is brought to you by' ... after about 6 ads are presented. Then another 4 to 6 ads are presented.

Who do they think they are kidding?

Out of frustration in hearing the same ads over-and-over, for

  • insurance
  • drugs
  • dating services
  • kitchen food-preparation products
  • home cleaning products
  • etc. etc.

none of which I am going to buy (especially now that they have wasted many, many hours of my life that I cannot get back), I decided to make a Tk GUI that facilitates gathering data on just how much time is being wasted on ads --- and to facilitate gathering data, over time, on 'advertising creep' --- and 'advertising creeps'.

I recently heard an older actor, who acted in situation comedies back in the 1970's, say that there were about 10 minutes of ads per hour in those days. I also recently heard, in an interview with a TV executive, that there are typically about 20 minutes of ads per hour nowadays.

So it is a known fact, by people 'in the biz', that there is much more advertsing per hour nowadays.

I wonder how soon we will have 30 minutes of ads per hour --- ON PAY CABLE TV CHANNELS. We are paying to be bombarded by all these ads!! (Watch your cable billing. There is cable-fee creep as well as ads creep.)

It is my hope the following Tk GUI may help in gathering data to support action, such as legislation or class-action suits, to put a stop to the 'ads creep'.

---

DESIGN OF THE GUI:

My first thoughts about the GUI concerned making it very easy to enter start and end times of 'ad segments'.

I decided to use 3 'scale' widgets --- to enter the hour:minute:second of the START of an 'ad segment' and to enter the END of the segment.

And below those 3 scale widgets, I envisioned having about 15 to 20 lines of START-time and END-time 'entry' widgets.

Since there are typically about 4 ad-segments per half-hour of programming ('content' plus ads), that means we need about 8 lines for an hour of programming --- and about 16 lines for a 2-hour movie.

I also wanted to have a 'Total' button, to generate the grand-total of advertising time.

I decided to show the Total time in the format 'mmm:ss' --- minutes and seconds --- no hours --- since, hopefully, we will generally not have more than about 60 minutes of ads, even in a long movie.

For the format in entering the START and END times, on the other hand, I wanted to make it easy to enter the times in an 'hh:mm:ss' format --- and allow the user to use 12-hour or 24-hour time specification.

To allow the user to specify AM, PM, or 24-hour (military/European) time, I decided to put 3 radiobuttons at the top of the GUI, over the 3 'scale' widgets.

I set to work making a GUI with these features, and I ended up with the GUI seen in the following image.

tkAdsAdder_screenshot_601x636.jpg

In the course of developing the GUI, I realized that I needed to add a 'Clear' button to the GUI --- so that the user can quickly clear all the entry fields (and fields showing calculated mmm:ss time data).

Also, besides the 'Total' button, I decided to add a 'Report' button --- to allow for generating a detailed report, listing all the start and end times for all the ad-segments --- as well as the grand total of my/our wasted time.

The 'Help' button on the GUI describes how to use the GUI. The following summary section from the Help text outlines the 'workflow'.

TYPICAL SEQUENCE OF OPERATIONS:

1) Set a radiobutton on the left of the 'ad-segment line' in which you want to make a start-time or end-time entry.

2) Check the AM/PM/24 radiobuttons at the top of the GUI to see if they are set according to the way you want to enter the time.

3) Set the hour, minute, second sliders to the time that you want to enter.

4) Click on the 'EnterStartTime' or 'EnterEndTime' button to transfer the time setting from the 3 scale widgets to an entry field on the 'ad-segment line' that you have selected.

5) Continue using steps 1 through 4 to enter start and end times for the ad segments in a TV program.

6) When finished entering start and end times, click on the 'Total-All-Ads' button to show a total time, beside the 'Total-All-Ads' button.

Use the 'Report' button for a more detailed report.


The code

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

I follow my usual 'canonical' structure for Tk code for this Tk script:

  0) Set general window & widget parms (win-name, win-position,
     win-color-scheme, fonts, widget-geometry-parms, win-size-control,
     text-array-for-labels-etc).

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

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

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

  4) Define PROCS, if needed.

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

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

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

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


Experimenting with the GUI

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

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

In this particular GUI, I allowed the user to resize the window.

However, one could un-comment the statement

   wm resizable . 0 0

and then the user would not be able to change the size of the window from its initially built size.

With the window resizable, 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 as the window is resized.

Actually, I fixed the y-size of the window, but allowed the x-size to vary, with the statement:

   wm resizable . 1 0

___

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.


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 simply

    'enterStartTime_fromScales' - called by the 'EnterStartTime' button.

    'enterEndTime_fromScales'   - called by the 'EnterEndTime'   button.

    'calcSEGlength_mmmss'       - called by the 2 procs above, if
                                  both entries in the current line are
                                  available.

    'clear_all'                - called by the 'ClearAll' button.
                                 Clears all the start & end 'entry' fields
                                 and the seg-length and total 'text' fields.

    'total_all_ads'          - called by the 'Total-All-Ads' button.

    'Report'                 - called by the 'Report' button.

    'popup_msg_var_scroll' - called by the 'Help' and 'Report' buttons.

---

One rather unique thing about this GUI is that whenever the user clicks on the AM/PM/24-hour radiobuttons, the '-from' and '-to' parameters of the HOURS scale widget is set to 1-and-12 or 0-and-23.

This is the first time I have ever found a need to allow for changing the max and min values of a 'scale' widget. You can see how easily that was accomplished by looking at the 'BINDINGS section' of the code.


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 that are often prefixed by unavoidable ads. (They're everywhere!)


 Code for Tk script 'tkAdsAdder.tk' :
#!/usr/bin/wish -f
##
##+#######################################################################
## NOTE:
##   If the 'wish' interpreter is in another directory, like
##   /usr/local/bin, you, as root, can make a soft-link from 'wish'
##   in that directory to /usr/bin/wish --- with a command like
##             ln -s /usr/local/bin/wish  /usr/bin/wish
##   The form of this command:
##             ln -s <filename-of-existing-file> <name-of-new-link-file>
##+#######################################################################
## Tk SCRIPT NAME:   tkAdsAdder.tk
##+#######################################################################
## PURPOSE:  This Tk GUI script provides a GUI for adding up the
##           amount of time spent on commercials (ads) during a
##           TV show.
##
##           Up to Nsegs commercial segments are accomodated, where
##           Nsegs is about 16, but could be set higher. This is
##           (hopefully) enough segments to handle half-hour and hour
##           shows (such as most situation comedies and crime dramas)
##           --- and even most movies shown on commercial TV.
##
##           (In 2013, 'commercial segments' were typically composed of
##            about 4 to 12 individual ads. The segment is/was often split into
##            two 'psuedo-segments' by an annuouncer saying something like
##            'Stay tuned for ...' or 'Closed captioning is brought to you by
##            after about 6 ads were presented, and then another
##            4 to 6 ads are presented. Since no real program content is
##            presented during those 10 to 12 ads, the start and end time
##            of the entire set of about a dozen ads should be entered ---
##            i.e. treat the entire mess as one 'segment'. Who do they
##            think they are kidding?)
##
##+##############
## THE GUI LAYOUT:
##
##  The GUI widgets are to be laid out according to the following
##  'text-sketch' of the GUI, where
##
## - Braces indicate a Tk 'button' widget.
## - Underscores indicate a Tk 'entry' widget.
## - A colon indicates that the text before the colon is on a Tk 'label' widget.
## - Capital-O indicates a Tk 'radiobutton' widget.
## - Capital-X indicates a Tk 'checkbutton' widget.
## - A line (hyphens) with an arrow-head at each end indicates a Tk 'scale' widget.
## - Vertical bars indicate the start and end of a text line (in a Tk 'text' or 'label' widget).
## - Square brackets indicate a comment (not to be shown on the GUI).
## 
## -------------------------------------------------------------------------------
## tkAdsAdder - to add the irretrievable am't of YOUR time consumed by freakin' ads   [window title]
## -------------------------------------------------------------------------------
## {Exit} {Help} {Total-All-Ads} Total(mm:ss): |999:99| {Report} {ClearAll}
## 
## Hour of Day:  O AM  O PM  O 24-hour
## Hour:   <---------------O----------------> |0-23|
## Minute: <---------------O----------------> |0-59|
## Second: <---------------O----------------> |0-59|
## {EnterStartTime} {EnterEndTime} in hh:mm:ss, in selected Segment line.
## O Ad Segment 1   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 2   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 3   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 4   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 5   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 6   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 7   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 8   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 9   Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 10  Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 11  Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 12  Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 13  Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 14  Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 15  Start: ______   End: ________  Length(mmm:ss): |.....|
## O Ad Segment 16  Start: ______   End: ________  Length(mmm:ss): |.....|
## ----------------------------------------------------------------------
## 
## This GUI will contain about:
## 
##    6 'button' widgets
##   53 'label'  widgets (or more)
##   32 'entry'  widgets
##    3 'scale'  widgets
##   19 'radiobutton' widgets
##   17 'text' (or 'label') widgets
##    0 'checkbutton' widgets
##    0 'listbox'     widgets
##
##+###############################
## METHOD OF OPERATION OF THE GUI:
##
##    Two entry fields are provided for each 'commercial segment'
##    --- to enter start-time and end-time for each 'segment' of ads.
##
##    The reason for the 3 hour/minute/second 'scale' widgets on
##    the GUI is to make the entry of time (in format hh:mm:ss)
##    easy for the keyboard-challenged user.  
##
##    The start-time and end-time are entered in hours:mins:secs,
##    for each entry field. (3 radiobuttons, above the 3 'scale' widgets,
##    may be used to indicate whether the user wants to specify the
##    start/end time as AM or PM or 24-hour time, via the scale widgets.)
##
##    Three 'scale' widgets (with slider buttons) are provided for entering
##    hours, minutes, and seconds quickly by dragging the slider button
##    of each of the 3 slider bars.
##
##    A radiobutton is provided in front of each pair of start/end
##    'entry' widgets --- to indicate which ad-segment is being
##    specified. Clicking on the 'EnterStartTime' or 'EnterEndTime'
##    button then determines whether the 3 'scale' widget settings
##    are put in the start or the end entry field for the
##    user-specified ad-segment.
##
##    After all the pairs of entry fields for a TV program are entered,
##    the user can click on the 'Total-All-Ads' button, and calculations are
##    performed to provide the total time consumed by all of the ads.
##
##    The calculations include subtracting each end-time and start-time
##    pair to get the mins:secs of each ad segment --- and then
##    adding up the minutes and seconds to get the total time of the
##    ads over the entire program that was watched.
##
##    The total time (hrs:mins:secs or mins:secs) is displayed in a small,
##    one-line text widget on the GUI. The user can copy-and-paste that time
##    from this GUI to another GUI, such as a text editor GUI.
##
##    A 'Report' button allows the user to generate a 'detailed' text report
##    that includes all the start and end times and the differences
##    that went into the total figure.
##
##    The report is shown in a popup scrollable Tk text widget,
##    from which the user may copy and paste the entire text into
##    another window, such as a text editor or HTML editor or
##    word-processor window.
##
##+########################################################################
## 'CANONICAL' 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).
##  1b) Pack   ALL the frames and sub-frames.
##  2) Define & pack all widgets in the frames, frame by frame.
##
##  3) Define key and mouse/touchpad/touch-sensitive-screen 'event'
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically with one or two
##                procs in section 4), if needed.
##
##+#################################
## The code structure in more detail, for this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :   (top to bottom)
##       'fRbuttons'
##       'fRtimetype'
##       'fRscaleH'
##       'fRscaleM'
##       'fRscaleS'
##       'fRbuttons2'
##       'fRadseg1'
##        ...
##       'fRadseg$Nsegs'
##
##        Sub-frames : none
##
##  1b) Pack ALL frames, including sub-frames (if any).
##
##  2) Define & pack all widgets in the frames -- basically going through
##     frames & their interiors in top-to-bottom and/or left-to-right order:
##
##      'fRbuttons'    - to contain several buttons --- such as Exit,
##                       Help, Total-All-Ads, Report, and Clear.
##
##                         (Could also have a 'UseIt' button that
##                          returns the ads time-total to stdout, so
##                          this Tk script-GUI could be used to return the
##                          [hrs:]mins:secs to a calling script/program.
##
##      'fRtimetype'    - to contain a label and several radiobuttons
##      'fRscaleH'      - to contain a label and a scale widget
##      'fRscaleM'      - to contain a label and a scale widget
##      'fRscaleS'      - to contain a label and a scale widget
##
##      'fRbuttons2'    - to contain 2 buttons and a label widget.
##
##       'fRadseg1'     - to contain 1 radiobutton, 2 entry widgets, and several label widgets
##        ...             ...
##      'fRadseg$Nsegs' - to contain 1 radiobutton, 2 entry widgets, and several label widgets
##
##  3) Define BINDINGS:  See the comments above the BINDINGS section of the
##                       code below, to see if any bindings were added to
##                       augment/facilitate the actions of the button widgets on the GUI.
##                      
##
##  4) Define PROCS:
##     'enterStartTime_fromScales' - called by the 'EnterStartTime' button
##     'enterEndTime_fromScales'   - called by the 'EnterEndTime'   button
##
##     'total_all_ads'          - called by the 'Total-All-Ads' button
##     'Report'                 - called by the 'Report' button
##     'popup_msgVarWithScroll' - called by the 'Help' button
##
##  5) Additional GUI INITIALIZATION:  none, except for setting
##                                     the HELPtext var for the Help button.
##
##+#######################################################################
## DEVELOPED WITH:
##   Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala').
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##                                  showed   8.5 8.5   on Ubuntu 9.10
##    after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts.
##+########################################################################
## MAINTENANCE HISTORY:
## Created by: Blaise Montandon 2013dec13 Started on Ubuntu 9.10.   
## Changed by: Blaise Montandon 2014feb04 Finish defining the GUI. Start
##                                        writing the procs.
## Changed by: Blaise Montandon 2014feb05 Finish writing procs. Test.
## Changed by: Blaise Montandon 2014mar10 Used 'scan' with '%d' to convert some
##                                        hh:mm:ss text data to decimal --- to avoid
##                                        'invalid octal number' errors when trying
##                                        to do arithmentic with '08' & '09'.
##                                        ALSO:
##                                        Made logic changes so that times between
##                                        midnight and 1AM show as 00 hours, rather
##                                        than 24 --- in the start-end entry fields.
##                                        ALSO:
##                                        Added 'RELIEF_label_lo' & 'RELIEF_numtext'
##                                        & 'RELIEF_radbutt_hi' variables --- to be
##                                        able to easily set the relief of some of
##                                        the 'label'/'text'/'radiobutton' widgets ---
##                                        so that 'label' and 'text' widgets do NOT
##                                        look like 'button' widgets (which they did
##                                        when they were hard-coded to 'raised') ---
##                                        and so that 'radiobutton' widgets stand
##                                        out as buttons to be clicked-upon.
##+############################################################################

##+#################################
## SET THE TOP WINDOW NAME.
##+#################################

wm title . \
   "tkAdsAdder - adds YOUR time consumed by freakin' ads"

##  - to add up the times of multiple 'commercial segments'"

wm iconname . "tkAdsAdder"

# catch { wm title    . "$env(ADSADDER_WIN_TITLE)" }
# catch { wm iconname . "$env(ADSADDER_ICON_TITLE)" }


##+###################################
##  SET THE TOP WINDOW POSITION.
##+###################################

wm geometry . +15+30

# catch {eval wm geometry . "$env(ADSADDER_GEOM)" }


##+#######################################################################
## SET COLOR SCHEME (palette) FOR THE WINDOW.
##+#######################################################################

if {1} {
##  Gray palette 
set Rpal255 210
set Gpal255 210
set Bpal255 210
}

if {0} {
##  Bluish palette 
set Rpal255 100
set Gpal255 0
set Bpal255 255
}

if {0} {
##  Greenish palette 
set Rpal255 160
set Gpal255 255
set Bpal255 0
}


if {0} {
##  Redish palette 
set Rpal255 255
set Gpal255 0
set Bpal255 100
}
     
set hexCOLORpal [format "#%02X%02X%02X" $Rpal255 $Gpal255 $Bpal255]

tk_setPalette $hexCOLORpal


## Set color background for some widgets.

set entryBKGD   "#f0f0f0"
set radbuttBKGD "#f0f0f0"

set scaleBKGD   "#f0f0f0"
set scaleBKGD  "$hexCOLORpal"

set textBKGD    "#e0e0e0"
# set chkbuttBKGD "#f0f0f0"
# set listboxBKGD "#f0f0f0"


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

set guiFONTsize 14
set guiFONT_SMALLsize 12

## For variable width:

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

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

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

## For fixed width:

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

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

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


##+#####################################################################
## DEFINE (temporary) FONT-NAMES using 'font create'.
## The font names are to be used in '-font' widget specs below.
##
## Generally we use variable-width fonts for BUTTONS and LABELS
## --- and fixed-width fonts for ENTRY and LISTBOX widgets.
##
## If we do not need to keep text columns aligned, we may
## use variable-width font in TEXT and MESSAGE widgets.
##+#####################################################################

eval font create fontTEMP_button   $FONT_varwidth
eval font create fontTEMP_label    $FONT_varwidth
eval font create fontTEMP_scale    $FONT_varwidth
eval font create fontTEMP_varwidth $FONT_varwidth

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

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

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


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

## For BUTTON widgets:
   
set PADY_button 0
set PADX_button 0
set BDwidth_button 2
## We use relief "raised" for all 'button' widgets.

## For LABEL widgets:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2
## Relief must be flat, groove, raised, ridge, solid, or sunken.
set RELIEF_label_hi "solid"
set RELIEF_label_lo "flat"

## SCALE geom parameters:

set BDwidthPx_scale 2
set initScaleLengthPx 300
set scaleThickPx 10


## For ENTRY widgets:

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


## RADIOBUTTON widget geom settings:

set PADXpx_radbutt 0
set PADYpx_radbutt 0
set BDwidthPx_radbutt 1
set RELIEF_radbutt_hi "raised"

## For TEXT widgets:

set BDwidthPx_text 2
# set RELIEF_numtext "sunken"
  set RELIEF_numtext "ridge"
# set RELIEF_numtext "groove"


##+###################################################################
## Set a MINSIZE of the window (roughly). (OR fix the window size.)
##
## For WIDTH, allow for a minwidth of the '.fRbuttons' frame:
##            about 4 buttons (Exit,Help,Total-All-Ads,Report,Clear)
##            and a label and text for the total.
##
## For HEIGHT, allow about
##          1 char  high for the '.fRbuttons'  frame
##          1 char  high for the '.fRtimetype' frame
##          1 char  high for the '.fRscaleH'   frame
##          1 char  high for the '.fRscaleM'   frame
##          1 char  high for the '.fRscaleS'   frame
##          1 char  high for the '.fRbuttons2' frame
##          1 char  high for the '.fRadseg1'   frame
##          ...
##          1 char  high for the '.fRadseg$Nsegs'  frame
##       ---------------
##  Total  22 chars high
##+#######################################################################

set minWinWidthPx [font measure fontTEMP_button \
   " Exit  Help  Total-All-Ads  Total(mm:ss) 999:99  Report  Clear "]

## Add some pixels to account for right-left-side window decoration
## (about 8 pixels), about 6 widgets x 4 pixels/widget for borders/padding.

set minWinWidthPx [expr {32 + $minWinWidthPx}]


## MIN HEIGHT --- about 22 chars high:

set CharHeightPx [font metrics fontTEMP_button -linespace]

set minWinHeightPx [expr {22 * $CharHeightPx}]

## Add about 28 pixels for top-bottom window decoration,
## about 22 frames x 4 pixels/frame for each of the 22 stacked frames
## and their widgets (their borders/padding).

set minWinHeightPx [expr {116 + $minWinHeightPx}]


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

wm minsize . $minWinWidthPx $minWinHeightPx


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


##+########################################################################
## 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(buttonTOTAL)  "Total-All-Ads"
set aRtext(labelTOTAL)   " Total(mmm:ss):"
set aRtext(buttonREPORT) "Report"
set aRtext(buttonCLEAR)  "ClearAll"

## For '.fRtimetype' frame:

set aRtext(labelTIMETYPE) "Time type:"
set aRtext(radbuttAM)   "AM"
set aRtext(radbuttPM)   "PM"
set aRtext(radbutt24HR) "24-hr"
set aRtext(labelTIMETYPE2) "  ... for next Start/End entry."


## For '.fRscaleH' frame:

set aRtext(labelHRscale)  "Hour:"
set aRtext(labelHRscale2) "(1-12) or (0-23)"

## For '.fRscaleM' frame:

set aRtext(labelMINscale) "Minute:"
set aRtext(labelMINscale2) "(0-59)"

## For '.fRscaleS' frame:

set aRtext(labelSECscale)  "Second:"
set aRtext(labelSECscale2) "(0-59)"

## For '.fRbuttons2' frame:

set aRtext(buttENTERstart) "EnterStartTime"
set aRtext(buttENTERend)   "EnterEndTime"
set aRtext(labelSEGShead)  "in hh:mm:ss (24 hr format), in selected Segment line."

## For frames  '.fRadseg1' to  '.fRadseg16' :

set Nsegs 16

for {set k 1} {$k <= $Nsegs} {incr k} {
   set FMTDk [format "%02d" $k]
   eval set aRtext(radbuttSEGselect$k)  \"AdSeg$FMTDk \"
}

set aRtext(labelSEGstart)  "Start:"
set aRtext(labelSEGend)    "End:"
set aRtext(labelSEGlength) "   Length(mmm:ss):"


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


##+####################################################################
## DEFINE *ALL* THE FRAMES:
##
##   TOP-LEVEL FRAMES:
##
##      'fRbuttons'    - to contain buttons Exit, Help, Total-All-Ads,
##                       Report, Clear
##      'fRtimetype'    - to contain a label and several radiobuttons
##      'fRscaleH'      - to contain a label and a scale widget
##      'fRscaleM'      - to contain a label and a scale widget
##      'fRscaleS'      - to contain a label and a scale widget
##      'fRbuttons2'    - to contain 2 buttons and a label widget.
##
##      'fRadseg1'      - to contain 1 radiobutton, 2 entry widgets,
##                        and several label widgets
##        ...             ...
##        ...             ...
##     'fRadseg$Nsegs'  - to contain 1 radiobutton, 2 entry widgets,
##                        and several label widgets
##
##+####################################################################

## FOR TESTING:  (esp. expansion of frames, during window expansion)
# set RELIEF_frame raised
# set BDwidth_frame 2

set RELIEF_frame flat
set BDwidth_frame 0

frame .fRbuttons  -relief $RELIEF_frame  -bd $BDwidth_frame
frame .fRtimetype -relief $RELIEF_frame  -bd $BDwidth_frame
frame .fRscaleH   -relief $RELIEF_frame  -bd $BDwidth_frame
frame .fRscaleM   -relief $RELIEF_frame  -bd $BDwidth_frame
frame .fRscaleS   -relief $RELIEF_frame  -bd $BDwidth_frame
frame .fRbuttons2 -relief $RELIEF_frame  -bd $BDwidth_frame

# set RELIEF_frame raised
# set BDwidth_frame 2

for {set k 1} {$k <= $Nsegs} {incr k} {
   frame .fRadseg$k -relief $RELIEF_frame  -bd $BDwidth_frame
}

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

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

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

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

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

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

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

for {set k 1} {$k <= $Nsegs} {incr k} {
   eval pack .fRadseg$k \
      -side top \
      -anchor nw \
      -fill x \
      -expand 0
}



##+################################################################
## The frames are now defined and packed.
##+################################################################
## START DEFINING & PACKING WIDGETS WITHIN THEIR FRAMES. 
##+################################################################
##+################################################################


##+############################################################
## IN THE 'fRbuttons' frame -- DEFINE BUTTONS and 1 LABEL ---
## Exit, Help, Total-All-Ads, total-label, Report, Clear
## THEN PACK THE WIDGETS.
##+############################################################

button .fRbuttons.buttEXIT \
   -text "$aRtext(buttonEXIT)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {exit}

button .fRbuttons.buttHELP \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {popup_msgVarWithScroll .topHelp "$HELPtext"}
 
button .fRbuttons.buttTOTAL \
   -text "$aRtext(buttonTOTAL)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {total_all_ads}

label .fRbuttons.labelTOTAL \
   -text "$aRtext(labelTOTAL)" \
   -font fontTEMP_button \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

text .fRbuttons.textTOTAL \
   -font fontTEMP_text \
   -height 1 \
   -width 10 \
   -wrap none \
   -borderwidth $BDwidthPx_text \
   -relief $RELIEF_numtext \
   -bg $textBKGD

button .fRbuttons.buttREPORT \
   -text "$aRtext(buttonREPORT)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {Report}

button .fRbuttons.buttCLEAR \
   -text "$aRtext(buttonCLEAR)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {clear_all}


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

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttTOTAL \
     .fRbuttons.labelTOTAL \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.textTOTAL \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbuttons.buttREPORT \
     .fRbuttons.buttCLEAR \
   -side right \
   -anchor e \
   -fill none \
   -expand 0


##+########################################################
## IN THE 'fRtimetype' frame -- DEFINE 
## a LABEL widget and AM/PM/24 RADIOBUTTONs.
## THEN PACK THEM.
##+########################################################

label .fRtimetype.labelTIMETYPE \
   -text "$aRtext(labelTIMETYPE)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

radiobutton  .fRtimetype.radbuttAM \
   -text "$aRtext(radbuttAM)" \
   -font fontTEMP_button \
   -anchor w \
   -variable VAR_AMorPMor24 \
   -value "AM" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRtimetype.radbuttPM \
   -text "$aRtext(radbuttPM)" \
   -font fontTEMP_button \
   -anchor w \
   -variable VAR_AMorPMor24 \
   -value "PM" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

radiobutton  .fRtimetype.radbutt24 \
   -text "$aRtext(radbutt24HR)" \
   -font fontTEMP_button \
   -anchor w \
   -variable VAR_AMorPMor24 \
   -value "24" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt \
   -relief $RELIEF_radbutt_hi

## Initialize the radiobutton variable.

# set VAR_AMorPMor24 "AM"
set VAR_AMorPMor24 "PM"
# set VAR_AMorPMor24 "24"

label .fRtimetype.labelTIMETYPE2 \
   -text "$aRtext(labelTIMETYPE2)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

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

pack .fRtimetype.labelTIMETYPE \
     .fRtimetype.radbuttAM \
     .fRtimetype.radbuttPM \
     .fRtimetype.radbutt24 \
     .fRtimetype.labelTIMETYPE2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## IN THE 'fRscaleH' frame -- DEFINE 
## a LABEL widget and a SCALE widget and another LABEL widget.
## The scale is oriented horizontally.
## THEN PACK THEM.
##+########################################################

label .fRscaleH.label \
   -text "$aRtext(labelHRscale)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

if {"$VAR_AMorPMor24" == "24"} {
   set HRmin 0
   set HRmax 23
} else {
   set HRmin 1
   set HRmax 12
}

scale .fRscaleH.scale \
   -orient horizontal \
   -from $HRmin -to $HRmax \
   -resolution 1 \
   -digits 2 \
   -length $initScaleLengthPx \
   -variable VARhour \
   -font fontTEMP_scale \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -relief flat \
   -highlightthickness 0 \
   -width $scaleThickPx \
   -bg $scaleBKGD

##   -command {entry_update}

.fRscaleH.scale set 6

label .fRscaleH.label2 \
   -text "$aRtext(labelHRscale2)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

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

pack .fRscaleH.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscaleH.scale \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRscaleH.label2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## IN THE 'fRscaleM' frame -- DEFINE 
## a LABEL widget and a SCALE widget and another LABEL widget.
## The scale is oriented horizontally.
## THEN PACK THEM.
##+########################################################

label .fRscaleM.label \
   -text "$aRtext(labelMINscale)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

scale .fRscaleM.scale \
   -orient horizontal \
   -from 0 -to 59 \
   -resolution 1 \
   -digits 2 \
   -length $initScaleLengthPx \
   -variable VARminute \
   -font fontTEMP_scale \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -relief flat \
   -highlightthickness 0 \
   -width $scaleThickPx \
   -bg $scaleBKGD

##   -command {entry_update}

.fRscaleM.scale set 10

label .fRscaleM.label2 \
   -text "$aRtext(labelMINscale2)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

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

pack .fRscaleM.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscaleM.scale \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRscaleM.label2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## IN THE 'fRscaleS' frame -- DEFINE 
## a LABEL widget and a SCALE widget and another LABEL widget.
## The scale is oriented horizontally.
## THEN PACK THEM.
##+########################################################

label .fRscaleS.label \
   -text "$aRtext(labelSECscale)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

scale .fRscaleS.scale \
   -orient horizontal \
   -from 0 -to 59 \
   -resolution 1 \
   -digits 2 \
   -length $initScaleLengthPx \
   -variable VARsecond \
   -font fontTEMP_scale \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -relief flat \
   -highlightthickness 0 \
   -width $scaleThickPx \
   -bg $scaleBKGD

##   -command {entry_update}

.fRscaleS.scale set 30

label .fRscaleS.label2 \
   -text "$aRtext(labelSECscale2)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

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

pack .fRscaleS.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscaleS.scale \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRscaleS.label2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## IN THE 'fRbuttons2' frame -- DEFINE 
## two BUTTON widgets and a LABEL widget.
## THEN PACK THEM.
##+########################################################

button .fRbuttons2.buttENTERstart \
   -text "$aRtext(buttENTERstart)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {enterStartTime_fromScales}

button .fRbuttons2.buttENTERend \
   -text "$aRtext(buttENTERend)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -bd $BDwidth_button \
   -relief raised \
   -command {enterEndTime_fromScales}

label .fRbuttons2.label \
   -text "$aRtext(labelSEGShead)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label \
   -relief $RELIEF_label_lo

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

pack .fRbuttons2.buttENTERstart \
     .fRbuttons2.buttENTERend \
     .fRbuttons2.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0



##+########################################################
## IN THE 'fRadseg' frames --- Nsegs of them.
## In each frame, DEFINE a RADIOBUTTON widget and
## DEFINE a LABEL widget and 3 ENTRY widgets,
## for a Start time and an END time..
## THEN PACK each set of widgets, in the loop.
##+########################################################

for {set k 1} {$k <= $Nsegs} {incr k} {

   radiobutton .fRadseg$k.radbutt \
      -text "$aRtext(radbuttSEGselect$k)" \
      -font fontTEMP_button \
      -anchor w \
      -variable VAR_radbutt_segnum \
      -value "$k" \
      -selectcolor "$radbuttBKGD" \
      -padx $PADXpx_radbutt \
      -pady $PADYpx_radbutt \
      -bd $BDwidthPx_radbutt \
      -relief $RELIEF_radbutt_hi

   label .fRadseg$k.labelSEGstart \
      -text "$aRtext(labelSEGstart)" \
      -font fontTEMP_label \
      -justify left \
      -anchor w \
      -padx $PADXpx_label \
      -pady $PADYpx_label \
      -bd $BDwidthPx_label \
      -relief $RELIEF_label_lo

   set AR_24HHMMSSstart($k) ""

   eval entry .fRadseg$k.entrySEGstart \
      -textvariable AR_24HHMMSSstart($k) \
      -bg "$entryBKGD" \
      -font fontTEMP_entry \
      -width 8 \
      -relief sunken \
      -bd $BDwidthPx_entry

   label .fRadseg$k.labelSEGend \
      -text "$aRtext(labelSEGend)" \
      -font fontTEMP_label \
      -justify left \
      -anchor w \
      -padx $PADXpx_label \
      -pady $PADYpx_label \
      -bd $BDwidthPx_label \
      -relief $RELIEF_label_lo

   set AR_24HHMMSSend($k) ""

   eval entry .fRadseg$k.entrySEGend \
      -textvariable AR_24HHMMSSend($k) \
      -bg $entryBKGD \
      -font fontTEMP_entry \
      -width 8 \
      -relief sunken \
      -bd $BDwidthPx_entry

   label .fRadseg$k.labelSEGlength \
      -text "$aRtext(labelSEGlength)" \
      -font fontTEMP_label \
      -justify left \
      -anchor w \
      -padx $PADXpx_label \
      -pady $PADYpx_label \
      -bd $BDwidthPx_label \
      -relief $RELIEF_label_lo

   text .fRadseg$k.textSEGlength \
      -font fontTEMP_text \
      -height 1 \
      -width  9 \
      -wrap none \
      -bd $BDwidthPx_text \
      -relief $RELIEF_numtext \
      -bg $textBKGD


   ##+#################################################
   ## Pack the widgets in the frame '.fRadseg$k'.
   ##+#################################################

   pack .fRadseg$k.radbutt \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

   pack .fRadseg$k.labelSEGstart \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

   pack .fRadseg$k.entrySEGstart \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

   pack .fRadseg$k.labelSEGend \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

   pack .fRadseg$k.entrySEGend \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

   pack .fRadseg$k.labelSEGlength \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

   pack .fRadseg$k.textSEGlength \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

}
## END OF the LOOP to define-and-pack widgets in the frames 'fRadseg$k'


## Initialize the ad-seg-num radiobutton value.

set VAR_radbutt_segnum 1


## Set a couple of variables to use when right-justifying
## text inserted into the .fRadseg$k.textSEGlength text widgets.

set FILLERtext "         "
set FILLERnumchars [string length "$FILLERtext"]


##+#######################################
## END OF MAIN SECTION TO SETUP THE GUI.
## FRAMES AND WIDGETS ARE DEFINED.
##+#######################################

##+#######################################################################
##+#######################################################################
## BINDINGS section:
##    We could define button1-release bindings on radiobuttons OR
##    the HR-MIN-SEC sliders (scales) OR <Return> bindings on entry widgets.
##+#######################################################################
##+#######################################################################

bind .fRtimetype.radbuttAM  <ButtonRelease-1>  {
   .fRscaleH.scale configure -from 1 -to 12

   ## FOR TESTING:
   # puts "Binding on .fRtimetype.radbuttAM  was triggered."
}

bind .fRtimetype.radbuttPM  <ButtonRelease-1>  {
   .fRscaleH.scale configure -from 1 -to 12

   ## FOR TESTING:
   # puts "Binding on .fRtimetype.radbuttPM  was triggered."
}

bind .fRtimetype.radbutt24  <ButtonRelease-1>  {
   .fRscaleH.scale configure -from 0 -to 23

   ## FOR TESTING:
   # puts "Binding on .fRtimetype.radbutt24  was triggered."
}



##+#####################################################################
##+#####################################################################
## PROCS section:
##     'enterStartTime_fromScales' - called by the 'EnterStartTime' button.
##     'enterEndTime_fromScales'   - called by the 'EnterEndTime'   button.
##     'calcSEGlength_mmmss'       - called by the 2 procs above, if
##                                   both entries in the current line are
##                                   available.
##
##     'clear_all'                - clears all the start & end entry fields
##                                  and the length and total text fields
##
##     'total_all_ads'          - called by the 'Total-All-Ads' button
##     'Report'                 - called by the 'Report' button
##     'popup_msgVarWithScroll' - called by the 'Help' button
##+#####################################################################
##+#####################################################################


##+#####################################################################
## proc 'enterStartTime_fromScales'
##+##################################################################### 
## PURPOSE: Depending on the integer, $k, in the variable 'VAR_radbutt_segnum',
##          put an hh:mm:ss entry (in 24-hr time) in the Start entry field
##          of entry widget .fRadseg$k.entrySEGstart --- in other words,
##          put an hh:mm:ss value in array variable AR_24HHMMSSstart($k).
##
##          Also:
##          If both the start and end times of the $k ad-segment line
##          are non-empty, calculate the length of the ad-segment
##          in the form mmm:ss and put the result in the text widget
##          .fRadseg$k.textSEGlength.
##
## CALLED BY:  the 'EnterStartTime' button
##+#####################################################################

proc enterStartTime_fromScales {} {

   global VAR_radbutt_segnum VAR_AMorPMor24 \
          AR_24HHMMSSstart AR_24HHMMSSend

   ## FOR TESTING: (dummy out this proc)
   # return

   ########################################################
   ## Get hour,min,sec values from the current settings of
   ## the hour,minute,second scales.
   ########################################################

   set tempHR  [.fRscaleH.scale  get]
   set tempMIN [.fRscaleM.scale  get]
   set tempSEC [.fRscaleS.scale  get]

   ########################################################
   ## Convert the hour value to 24-hour time, if the 
   ## time-type is set to PM.
   ########################################################

   if {"$VAR_AMorPMor24" == "PM"} {
      if {$tempHR < 12} {
         set tempHR [expr {12 + $tempHR}]
      } else {
         set tempHR 0
      }
   }


   ######################################################################
   ## Use the integer in the variable 'VAR_radbutt_segnum' to set $k.
   ## Then put an hh:mm:ss entry (in 24-hr time) in the Start entry field
   ## of entry widget .fRadseg$k.entrySEGstart --- in other words,
   ## put an hh:mm:ss value in array variable AR_24HHMMSSstart($k).
   ######################################################################

   set k $VAR_radbutt_segnum

   set tempTIME \
   "[format "%02d" $tempHR]:[format "%02d" $tempMIN]:[format "%02d" $tempSEC]"

   set AR_24HHMMSSstart($k) "$tempTIME"


   ############################################################
   ## We have set the START time. Now if the END-time of the
   ## $k ad-segment line is also non-empty,
   ## calculate the length of the ad-segment in the form mmm:ss
   ## and put the result in the text widget ---
   ## .fRadseg$k.textSEGlength ---
   ## using the proc 'calcSEGlength_mmmss'.
   ############################################################

   set tempSTRING [string trim $AR_24HHMMSSend($k)]
   if {"$tempSTRING" != ""} {calcSEGlength_mmmss $k}

}
## END OF proc 'enterStartTime_fromScales'


##+#####################################################################
## proc 'enterEndTime_fromScales'
##+##################################################################### 
## PURPOSE: Depending on the integer, $k, in the variable 'VAR_radbutt_segnum',
##          put an hh:mm:ss entry (in 24-hr time) in the End entry field
##          of entry widget .fRadseg$k.entrySEGend --- in other words,
##          put an hh:mm:ss value in array variable AR_24HHMMSSend($k).
##
##          Also:
##          If both the start and end times of the $k ad-segment line
##          are non-empty, calculate the length of the ad-segment
##          in the form mmm:ss and put the result in the text widget
##          .fRadseg$k.textSEGlength.
##
## CALLED BY:  the 'EnterEndTime' button
##+#####################################################################

proc enterEndTime_fromScales {} {

   global VAR_radbutt_segnum VAR_AMorPMor24 \
          AR_24HHMMSSstart AR_24HHMMSSend

   ## FOR TESTING: (dummy out this proc)
   # return

   ########################################################
   ## Get hour,min,sec values from the current settings of
   ## the hour,minute,second scales.
   ########################################################

   set tempHR  [.fRscaleH.scale  get]
   set tempMIN [.fRscaleM.scale  get]
   set tempSEC [.fRscaleS.scale  get]

   ########################################################
   ## Convert the hour value to 24-hour time, if the 
   ## time-type is set to PM.
   ########################################################

   if {"$VAR_AMorPMor24" == "PM"} {
      if {$tempHR < 12} {
         set tempHR [expr {12 + $tempHR}]
      } else {
         set tempHR 0
      }
   }


   #################################################################
   ## Use the integer in the variable 'VAR_radbutt_segnum' to set $k.
   ## Then put an hh:mm:ss entry (in 24-hr time) in the End entry field
   ## of entry widget .fRadseg$k.entrySEGend --- in other words,
   ## put an hh:mm:ss value in array variable AR_24HHMMSSend($k).
   #################################################################

   set k $VAR_radbutt_segnum

   set tempTIME \
   "[format "%02d" $tempHR]:[format "%02d" $tempMIN]:[format "%02d" $tempSEC]"

   set AR_24HHMMSSend($k) "$tempTIME"


   ############################################################
   ## We have set the END time. Now if the START-time of the
   ## $k ad-segment line is also non-empty,
   ## calculate the length of the ad-segment in the form mmm:ss
   ## and put the result in the text widget ---
   ## .fRadseg$k.textSEGlength ---
   ## using the proc 'calcSEGlength_mmmss'.
   ############################################################

   set tempSTRING [string trim $AR_24HHMMSSstart($k)]
   if {"$tempSTRING" != ""} {calcSEGlength_mmmss $k}

}
## END OF proc 'enterEndTime_fromScales'


##+#####################################################################
## proc 'calcSEGlength_mmmss'
##+##################################################################### 
## PURPOSE: If the start and end hh:mm:ss entries for a given
##          ad-segment line, $k, are non-empty, this proc calculates
##          the length of the ad-segment in mmm:ss format ---
##          and puts the string in text widget
##           .fRadseg$k.textSEGlength.
## 
## CALLED BY: procs  'enterStartTime_fromScales' and
##            'enterEndTime_fromScales'    
##+#####################################################################

proc calcSEGlength_mmmss {k} {

   global VAR_radbutt_segnum FILLERtext FILLERnumchars \
          AR_24HHMMSSstart AR_24HHMMSSend

   ## FOR TESTING: (dummy out this proc)
   # return

   #################################################
   ## Get the start and end ad-segment times for the
   ## 'current' ad-segment line --- from the
   ## start and end entry fields of the current line.
   #################################################
   ## Split out the hours, minutes, seconds (which
   ## should be in the form hh:mm:ss, 24-hr time)
   ## from the 2 entry field variables.
   #################################################

   set STARTlist [split "$AR_24HHMMSSstart($k)" :]
   set ENDlist   [split "$AR_24HHMMSSend($k)"   :]

   ## FOR TESTING:
   #   puts "IN proc 'calcSEGlength_mmmss':"
   #   puts "STARTlist: $STARTlist"
   #   puts "ENDlist  : $ENDlist"

   set STARThh24 [lindex $STARTlist 0]
   set ENDhh24   [lindex $ENDlist   0]

   set STARTmm [lindex $STARTlist 1]
   set ENDmm   [lindex $ENDlist   1]

   set STARTss [lindex $STARTlist 2]
   set ENDss   [lindex $ENDlist   2]

   ## FOR TESTING:
   if {0} {
       puts ""
       puts "STARThh24: $STARThh24     ENDhh24: $ENDhh24"
       puts "STARTmm  : $STARTmm       ENDmm  : $ENDmm"
       puts "STARTss  : $STARTss       ENDss  : $ENDss"
   }

   ##########################################################
   ## If there are leading zeros on these integers, remove
   ## them to avoid 'invalid octal number as operand' errors
   ## from '08' and '09'. Ref: https://wiki.tcl-lang.org/15158
   ##########################################################

   set STARThh24 [scan $STARThh24 %d]
   set ENDhh24   [scan $ENDhh24   %d]

   set STARTmm [scan $STARTmm %d]
   set ENDmm   [scan $ENDmm %d]

   set STARTss [scan $STARTss %d]
   set ENDss   [scan $ENDss   %d]

   ## FOR TESTING:
   if {0} {
       puts ""
       puts "STARThh24: $STARThh24     ENDhh24: $ENDhh24"
       puts "STARTmm  : $STARTmm       ENDmm  : $ENDmm"
       puts "STARTss  : $STARTss       ENDss  : $ENDss"
   }

   ###########################################################
   ## If ENDhh24 is less than STARThh24 (for example, ENDhh24
   ## is 0 and STARThh24 is 23), add 24 to ENDhh24 --- so that
   ## subtracting operations below do not give a negative
   ## number for the time difference.
   ########################################################

   if {$ENDhh24 < $STARThh24} {
      set ENDhh24 [expr {$ENDhh24 + 24}]
   }

   ##########################################
   ## Convert start and end 'hh:mm' to 'mmm'.
   ##########################################

   set STARTmmm [expr {($STARThh24 * 60) + $STARTmm}]
   set ENDmmm   [expr {($ENDhh24 * 60)   + $ENDmm  }]

   ####################################################
   ## If START 'ss' (seconds) is greater than END 'ss',
   ## add 60 to END 'ss' and demote the END 'mmm' by 1.
   ####################################################

   if {$STARTss > $ENDss} {
      set ENDss  [expr {$ENDss + 60}]
      set ENDmmm [expr {$ENDmmm - 1}]
   }

   #########################################################
   ## Calculate the difference between start and end
   ## 'mmm' and 'ss'. These should now both be non-negative.
   #########################################################

   set diffMMM [expr {$ENDmmm - $STARTmmm}]
   set diffSS  [expr {$ENDss  - $STARTss }]

   ## FOR TESTING:
   if {0} {
       puts ""
       puts "ENDmmm  : $ENDmmm"
       puts "STARTmmm: $STARTmmm"
       puts "diffMMM: $diffMMM"
       puts ""
       puts "ENDss  : $ENDss"
       puts "STARTss: $STARTss"
       puts "diffSS : $diffSS"
   }

   ###################################################
   ## Set the 'mmm:ss' text string and put it in the
   ## text widget for the ad-segment length.
   ###################################################

   set tempLENGTH "$diffMMM:[format "%02d" $diffSS]"

   #########################################################
   ## Insert the text string into the text widget
   ## for the ad-segment length --- with right justification.
   #########################################################

   eval .fRadseg$k.textSEGlength delete 1.0 end
   eval .fRadseg$k.textSEGlength insert 1.0 \"$FILLERtext\"

   set insertIDX [expr {$FILLERnumchars - [string length "$tempLENGTH"]}]

   ## FOR TESTING:
   if {0} {
        puts ""
        puts "FILLERtext: |$FILLERtext|"
        puts "FILLERnumchars: $FILLERnumchars"
        puts "tempLENGTH: $tempLENGTH"
        puts "insertIDX: $insertIDX"
   }

   eval .fRadseg$k.textSEGlength insert 1.$insertIDX $tempLENGTH

   #########################################################
   ## To help the user avoid overlaying the data in this
   ## ad-segment-line, advance the radiobutton variable
   ## value which holds the segment-number.
   #########################################################

   set VAR_radbutt_segnum [expr {$k + 1}]

}
## END OF proc 'calcSEGlength_mmmss'


##+#####################################################################
## proc 'clear_all'
##+##################################################################### 
## PURPOSE: Clears all the start & end entry fields
##          and the length and total text fields.
##
## CALLED BY: the 'ClearAll' button
##+####################################################################

proc clear_all {} {

   global Nsegs AR_24HHMMSSstart AR_24HHMMSSend

   ## FOR TESTING: (dummy out this proc)
   # return

   ########################################################
   ## Clear the START and END entry variables in all the
   ## ad-segment rows, and clear the SEGlength text widget
   ## in each row.
   ####################################################### 

   for {set k 1} {$k <= $Nsegs} {incr k} {
      set AR_24HHMMSSstart($k) ""
      set AR_24HHMMSSend($k) ""
      eval .fRadseg$k.textSEGlength delete 1.0 end
   }
   ## END OF k-loop

   ######################################################
   ## Clear the total, if any, in the TOTAL text widget.
   ######################################################

   .fRbuttons.textTOTAL delete 1.0 end

   #########################################################
   ## To help the user start entering start/end times on
   ## the first ad-segment line, set the radiobutton variable
   ## value (which holds the segment-number) to one.
   #########################################################

   set VAR_radbutt_segnum 1
}
## END OF proc 'clear_all'


##+#####################################################################
## proc 'total_all_ads'
##+##################################################################### 
## PURPOSE: Totals the 'mmm:ss' values in the text widgets
##          that hold the ad-segment lengths:
##          .fRadseg$k.textSEGlength
##
## CALLED BY: the 'Total-All-Ads' button
##+#####################################################################

proc total_all_ads {} {

   global Nsegs

   ## FOR TESTING: (dummy out this proc)
   # return

   ########################################################
   ## Add up the 'mmm:ss' values from the up-to-Nsegs
   ## text widgets that hold the ad-segment lengths:
   ##          .fRadseg$k.textSEGlength
   ##
   ## We add the 'mmm' and 'ss' values separately, and
   ## later deal with the 'ss' total if it goes over 59.
   ####################################################### 

   set TOTmmm 0
   set TOTss 0

   for {set k 1} {$k <= $Nsegs} {incr k} {
      set tempSEGLEN [.fRadseg$k.textSEGlength get 1.0 end]
      set tempSEGLEN [string trim "$tempSEGLEN"]
      if {"$tempSEGLEN" != ""} {
         set SEGLENlist [split "$tempSEGLEN" :]
         set tempMMM [lindex $SEGLENlist 0]
         set tempSS  [lindex $SEGLENlist 1]

         ##########################################################
         ## If there are leading zeros on tempSS, remove
         ## them to avoid 'invalid octal number as operand' errors
         ## from '08' and '09'. Ref: https://wiki.tcl-lang.org/15158
         ##########################################################

         set tempSS   [scan $tempSS   %d]

         ## FOR TESTING:
         if {0} {
             puts ""
             puts "tempSEGLEN: $tempSEGLEN"
             puts "SEGLENlist: $SEGLENlist"
             puts "tempMMM: $tempMMM"
             puts "tempSS: $tempSS"
         }

         set TOTmmm [expr {$TOTmmm + $tempMMM}]
         set TOTss  [expr {$TOTss + $tempSS}]
      }
      ## END OF if {"$tempSEGLEN" != ""}
   }
   ## END OF k-loop

   #################################################
   ## If TOTss > 59, convert the excess to minutes
   ## and add the minutes to TOTmmm.
   #################################################

   if {$TOTss > 59} {
      set tempMIN [expr {int($TOTss/60.0)}]
      set TOTss [expr {$TOTss - (60 * $tempMIN)}]
      set TOTmmm [expr {$TOTmmm + $tempMIN}]
   }

   #################################################
   ## Put 'TOTmmm:TOTss' in the TOTAL text widget.
   #################################################

   .fRbuttons.textTOTAL delete 1.0 end
   .fRbuttons.textTOTAL insert 1.0 "${TOTmmm}:[format "%02d" $TOTss]"

}
## END OF proc 'total_all_ads'


##+#####################################################################
## proc 'Report'
##+##################################################################### 
## PURPOSE: Forms a 'detailed' report consisting of:
##          - start & end time hh:mm:ss data for all the ad-segments entered
##          - the mmm:ss time lengths from the text widgets
##            (.fRadseg$k.textSEGlength)
##          - the total ad time (sum of the mmm:ss lengths).
##          
## CALLED BY: the 'Report' button
##+#####################################################################

proc Report {} {

   global Nsegs AR_24HHMMSSstart AR_24HHMMSSend

   ## FOR TESTING: (dummy out this proc)
   # return

   #################################
   ## Get the date in a nice format.
   #################################

   set DATEstring [clock format [clock seconds] \
      -format "%Y %b %d %a %H:%M:%S %Z"]

   #####################################
   ## Prepare the header of the report.
   #####################################

   set REPORTtext "Ad-Time Report                 $DATEstring

showing ad start-time and end-time details
and a grand-total-time in 'mins:secs' format.

The following start and end times are shown in
'hh:mm:ss' format, in 24-hour time format.

Ad
Segment
Number   START-time   END-time   LENGTH (mmm:ss)
------  -----------  ----------  --------------
"

   #####################################################
   ## Prepare the up-to-Nsegs detail lines of the report,
   ## while collecting mins,secs totals for the grand total.
   #####################################################

   set TOTmmm 0
   set TOTss 0

   for {set k 1} {$k <= $Nsegs} {incr k} {
      set tempSEGLEN [.fRadseg$k.textSEGlength get 1.0 end]
      set tempSEGLEN [string trim "$tempSEGLEN"]
      if {"$tempSEGLEN" != ""} {
         set SEGLENlist [split "$tempSEGLEN" :]
         set tempMMM [lindex $SEGLENlist 0]
         set tempSS  [lindex $SEGLENlist 1]
         set tempSS   [scan $tempSS   %d]
         set TOTmmm [expr {$TOTmmm + $tempMMM}]
         set TOTss  [expr {$TOTss + $tempSS}]
         set REPORTtext "$REPORTtext
[format "%6s" $k]  [format "%10s" $AR_24HHMMSSstart($k)] \
[format "%10s" $AR_24HHMMSSend($k)] [format "%6s"  $tempSEGLEN]"
      }
      ## END OF if {"$tempSEGLEN" != ""} 
   }
   ## END OF k-loop

   #################################################
   ## If TOTss > 59, convert the excess to minutes
   ## and add the minutes to TOTmmm.
   #################################################

   if {$TOTss > 59} {
      set tempMIN [expr {int($TOTss/60)}]
      set TOTss [expr {$TOTss - (60 * $tempMIN)}]
      set TOTmmm [expr {$TOTmmm + $tempMIN}]
   }

   #####################################
   ## Prepare the trailer of the report.
   #####################################

   set REPORTtext "$REPORTtext

                 TOTAL Ad Time : ${TOTmmm}:[format "%02d" $TOTss] (mins:secs)


This report was prepared by the 'tkAdsAdder' Tk GUI script."

   #####################################
   ## Show the report in a popup window.
   #####################################

   popup_msgVarWithScroll .topReport "$REPORTtext"
}
## END OF proc 'Report'


##+########################################################################
## 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} {
      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_varwidth \
         -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} {
      ## 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 PROCS SECTION.
##+###############################################
## Setting of HELPtext variable follows.
##+###############################################

set HELPtext "\
*** HELP for this 'tkAdsAdder' GUI ****

This Tk GUI script provides a GUI for adding up the amount
of time spent on commercials (ads) during a TV show.

Up to Nsegs commercial segments are accomodated, where
Nsegs is about 16, but could be set higher. This is
(hopefully) enough segments to handle half-hour and hour
shows (such as most situation comedies and crime dramas)
--- and even most movies shown on commercial TV.

(In 2013, 'commercial segments' in a TV program were
 typically composed of about 4 to 12 individual ads.

 The typical 'ads segment' is often split into TWO
 'psuedo-segments' by an annuouncer saying something like
 'Closed captioning is brought to you by' ... after about
 6 ads are presented. Then another 4 to 6 ads are presented.
 Who do they think they are kidding?

 Since no real program content is presented during those
 10 to 12 ads, the start and end time of the entire
 set of about a dozen ads should be entered ---
 i.e. treat the entire mess as one 'segment'.)

******************************
METHOD OF OPERATION OF THE GUI:

Two entry fields are provided for each 'segment of commercials'
--- to enter a start-time and end-time for each 'segment' of ads.

Three 'scale' widgets are at the top of the GUI --- to set
an hour, minute, and second --- for the the start and end
times of an ad segment.

The 3 'scale' widgets are intended to make the entry of time
(in the format hh:mm:ss), in each of the entry fields,
easy for the keyboard-challenged user.

The 'start' or 'end' hours, minutes, and seconds can be entered
quickly by dragging the slider button of each of the 3 slider bars.

Three radiobuttons, above the 3 'scale' widgets, are to be used
to indicate whether the user wants to specify the start/end time
as AM or PM or 24-hour time, via the scale widgets.

A radiobutton is provided in front of each pair of start/end
'entry' widgets --- to indicate which ad-segment is being
specified. After clicking on one of the ad-segment radiobuttons,
clicking on the 'EnterStartTime' or 'EnterEndTime' button
then determines whether the 3 'scale' widget settings are put into
the start or the end entry field for the user-specified ad-segment.

Whenever a start-time or an end-time is entered or changed --- and if
a start-time and end-time are both non-empty on an 'ad-segment line' ---
a calculation is performed to give the length of the ad-segment in the
form mins:secs --- shown on the right-side of the 'current-ad-segment'
line.

-------------------------
GETTING THE 'GRAND-TOTAL':
-------------------------

After all the pairs of entry fields for a TV program are entered,
the user can click on the 'Total-All-Ads' button, and calculations are
performed to provide the total time consumed by all of the ads.

When the 'Total-All-Ads' button is clicked, the 'mmm:ss' time-lengths
are added up to give the total time of the ads over the entire
time that the program ad-times were recorded.

The total time (in mmm:ss format) is displayed in a small, one-line
text widget on the GUI, next to the 'Total-All-Ads' button.
The user can copy-and-paste that time from this GUI to another GUI,
such as a text editor GUI.

A 'Report' button allows the user to generate a 'detailed' text report
that includes all the start and end times and the differences
(ad-segment lengths) that went into the total figure.

The report is shown in a popup scrollable Tk text widget,
from which the user may copy and paste the entire text into
another window, such as a text editor or HTML editor or
word-processor window.

******************************
TYPICAL SEQUENCE OF OPERATIONS:

1) Set a radiobutton on the left of the 'ad-segment line' in which
   you want to make a start-time or end-time entry.

2) Check the AM/PM/24 radiobuttons at the top of the GUI to see if
   they are set according to the way you want to enter the time.

3) Set the hour, minute, second sliders to the time that you want
   to enter.

4) Click on the 'EnterStartTime' or 'EnterEndTime' button to
   transfer the time setting from the 3 scale widgets to an entry
   field on the 'ad-segment line' that you have selected.

5) Continue using steps 1 through 4 to enter start and end times
   for the ad segments in a TV program.

6) When finished entering start and end times, click on the
   'Total-All-Ads' button to show a total time, beside the
   'Total-All-Ads' button.

   Use the 'Report' button for a more detailed report.


***************************
FINE CONTROL OF THE SLIDERS:

Most people know that you can drag the sliders by clicking
on the 'slider button' with mouse-button1 and dragging the
slider button. This is a rather coarse way of moving the
slider button.

It is not so obvious that the Tk scale widget also allows
you to move the 'slider button' by clicking in the 'trough'
on EITHER SIDE of the slider button. By repeated clicking
in the trough, the slider can be advanced one scale-resolution
unit per click.

Furthermore, one can RAPIDLY move the slider button ONE
RESOLUTION UNIT per 'auto-click' by clicking in the trough
and HOLDING down the mouse button. The slider moves
one resolution unit repeatedly UNTIL the mouse button
is RELEASED.

*************************
SETUP of this utility:

Note that the user can set up this script as an icon on a
computing-device 'desktop' so that the GUI can be started up
by a click (or two) on the icon --- say, while the user is
watching a ad-filled TV program.

For example, put the Tk script into a sub-directory of a
user's home directory, such as \$HOME/apps/tkAdAdder.
"

##+###############################################
## ADDITONAL GUI INITIALIZATION (if any) FOLLOWS.
##+###############################################
## none for now
##+###############################################


SOME POTENTIAL ENHANCEMENTS:

Data May Be Overwritten Warnings

I have tried to make it a bit difficult for the user to accidentally wipe out some previously entered data for an ad-segment while trying to enter some data for a new ad-segment.

When both the START and the END times are entered on a line, the LENGTH of that ad-segment is calculated and shown. Also, at that time, the ad-segment radiobutton setting is advanced a line so that the user is unlikely to wipe out data on previously entered lines.

However, it may still be the case that the user can accidentally wipe out data. If that proves to be a problem, a warning popup could appear, whenever the user clicks on the 'EnterStartTime' or 'EnterEndTime' buttons and there is already Start or End time data on the 'currently active' line (as indicated by the radiobuttons at the start of the lines).

I plan to be using this GUI in coming months, so if data-wipeout turns out to be a problem, I may add warning-popup logic to the code and replace the code here.

---

Subtraction Logic - Lurking Bugs?

There is special arithmetic processing involved in dealing with situations when the Start-time is being subtracted from the End-time --- such as when Start-seconds are greater than End-seconds.

In that case, the End-seconds are augmented by 60 seconds and a minute is deducted from the End-minutes.

There may be a situation like that that I have not anticipated.

In coming months of using this utility, if I discover a case like that that needs some special logic added, I will add the logic and replace the code here.


IN CONCLUSION

It makes me smile that I can use Tcl-Tk to make GUI utilities like this --- utilities that make a tedious task much easier. This utility can perform all that subtraction and totalling up (with carrying operations between seconds and minutes and hours) in a fraction of a second --- compared to my multiple minutes for 'manual' recording and calculation. Hundreds of times faster.

As I have said on quite a few 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 2014mar10

Well, as I mentioned above in the 'Lurking Bugs?' section:

"In coming months of using this utility, if I discover a case ... that needs some special logic added, I will add the logic and replace the code here."

In fact, on a first test of this utility (after not looking at it in the month after publishing the code), not only did I find a bug (an 'invalid octal number' error), but I found some confusion in using the utility because the 'Start:' and 'End:' labels looked like buttons, since I used '-relief raised' to make those labels.

I found that I needed to change the relief of those labels so that they do not look like 'button' widgets --- to be clicked upon.

The new look of the GUI is seen in the following image.

tkAdsAdder_version2_screenshot_604x636.jpg

I made the following changes to the code and replaced the code above with the new code.

1) I used 'scan' with '%d' to convert some hh:mm:ss text data to decimal --- to avoid 'invalid octal number' errors when trying to do arithmentic with '08' and '09'.

2) I made logic changes so that times between midnight and 1AM show as 00 hours, rather than 24 --- in the start-and-end entry fields.

3) I added 'RELIEF_label_lo' and 'RELIEF_numtext' and 'RELIEF_radbutt_hi' variables --- to be able to easily set the relief of some of the 'label', 'text', and 'radiobutton' widgets --- so that 'label' and 'text' widgets do NOT look like 'button' widgets (which they did when they were hard-coded to be 'raised') --- and so that 'radiobutton' widgets stand out as buttons to be clicked-upon.


An Alternative design (proposed by RLE)

RLE (2014-03-10): This is an interesting concept, but I think you might consider a radically simplified GUI if your plan is to utilize this GUI while actually watching a TV show. My reason is that the time it will take to use sliders to pick a start and end time will rapidly result in one getting tired of collecting the data, or result in one missing a few minutes of the show when it returns to the air after the ad. break.

Instead, consider a super simple GUI like so (not really to scale):

 +--------------------------+
 | +----------------------+ |
 | | Really Big Start/Stop| |
 | | Button               | |
 | +----------------------+ |
 |                          |
 | +----------------------+ |
 | | Text widget to log   | |
 | | human readable       | |
 | | segment length lines | |
 | |                      | |
 | +----------------------+ |
 +--------------------------+

The "really big start stop button" (RBB) would begin with a label of "Start" or "Segment Start" or "Commercial Start", whatever is appropriate. Pressing the button causes the code to capture a clock seconds value (a begin value) and store it away somewhere.

Once pressed, the button label would toggle to say "Stop" or "End of Segment" or "End of Commercials", whatever is appropriate. Pressing it again would capture a second clock seconds value (an end value).

Now, with a begin and end value, you can do several things:

  1. Compute a duration by subtracting the end from the begin value and using clock format to format a human readable MM:SS value for the text widget log. By using Tcl clock seconds values you avoid having an end prior to a begin, and have no need for scan %d to remove leading zeros.
  2. Internally store the raw clock seconds values as a list of lists (lappend data list $begin $end). This internal list of lists can be what you save to disk, by simply puts'ing the list to an open file. As it contains all the data you can use it to recreate the human readable log lines at any time by simply iterating across it, subtracting, and clock formating.

The advantages you get are:

  1. Easy to use while watching tv. As long as the mouse cursor is over the RBB, you just have to hit the mouse button at the start, and again at the end, of the commercial breaks. No need to spend time adjusting sliders to wall clock values, let Tcl handle the "what time is it now" factor internally. If you are using a laptop with a touchpad, then tracking might be as easy as just tapping the pad once at both boundaries.
  2. Avoidance of math errors from odd data entry values. The worst case is a zero difference, and then only if you click the button twice within the same second of time. Otherwise the end value will always be later than the start value.
  3. A compact save format. It just stores the raw clock values. No need to save the human readable elements when they can be regenerated. Plus the save format is suitable to being combined with other saved files into larger totals over days/weeks of time.
  4. Very easy totaling. Just iterate the internal list, subtract the two sub-list elements, and accumulate a total. If you are using tcl 8.6 totaling the list would likely be a one line lmap operation.

---

uniquename 2014mar13

Nice idea. Why don't you code it up and start a new page for it. (This one is getting pretty long.) It's your idea. I don't want to steal it.

(If you have not coded it up by 2015 March, is it OK if I provide a version --- if I am still around then? In the meantime, I have about 100 scripts on my 'to-do' list to keep me busy --- as seen on my 'bio' page at uniquename.)

By the way, I don't find the scales very hard to use --- especially since changing to the next time (a start or end time) usually means just clicking on one side (often the right side) of the 'slider button' and holding mouse-button-1 down to quickly advance the slider. I usually have to do that on just the minutes and seconds sliders --- not the hour.

Besides, this approach works even for those people who never set the proper date and time on their computers --- which seems to happen pretty frequently judging from emails that I have seen with totally erroneous years in their Sent-Times (not to mention the month, day, and time-of-day).

---

RLE (2013-03-14): I might, provided I find time to do so. I'm fine if you want to code up a version as well.

You are quite correct, I am assuming a correctly set computer clock. But as I've kept all my computers slaved to NTP internet time for 10+ years now, I've long since forgotten about having a computer clock that is out of sync with real time.

---

uniquename 2014mar16: I won't be coding your design anytime soon. You are making another assumption. You are assuming that the user will never accidentally make an erroneous click. If you assume that, it would be like proceeding to make a database application that only does 'adds' --- but no 'changes' or 'deletes' --- the other two crucial operations needed for a database system.

To me, the time-consuming part would be in designing and coding a nice way for the user to apply 'changes' and 'deletes' to that 'internal list' that you mention.

RLE (2014-03-21): Done, see the Ad Break Timer page.