Updated 2014-06-07 21:50:19 by uniquename

uniquename - 2013aug14

In late 2012, I made several alternate Tk GUI's for drawing color gradients across a Tk canvas --- using 'create line' --- and either vertical or horizontal lines of a color gradiating between two user-selectable colors.

Example: A color-gradient-button-maker GUI using a separate color-selector script

This kind of Tk canvas GUI is useful for making BIBB's --- buttons, icons, backgrounds, and bullets. In particular, I was interested in having a utility to make nice looking backgrounds for Tk buttons or 'toolchests'.

I have had it on my 'to-do' list for many months to make a similar utility (or two or three) that allowed for using 'curvalicious' lines for making the color-gradient image.

I decided recently to try making such a utility --- using 'create oval', instead of 'create line' --- to make a family of color-gradiating ovals, instead of a family of color-gradiating straight lines.

And I wanted to give the user the ability to quickly choose the 'center point' for the family of ovals. And, of course, I wanted to give the user the ability to choose the colors (from a palette of 16-million-plus colors).

And, since we are dealing with ovals, I needed to provide a means of choosing the shape of the ovals, so I put a 'scale' widget on the GUI --- by which the user can choose an 'aspect ratio' for the ovals.

After several iterations of minor changes to the widgets on the GUI (and the procs being used), I ended up with the GUI seen in the following image.

This is an image of the GUI as it first pops up --- showing that the two gradient colors are defaulted to white and blue --- and that the 'aspect ratio' of the ovals is initialized to 2.0.

The sequence of ovals is intended to yield a nicely color shaded image, with a highlight in any portion of the image.

EXTENT OF THE OVALS and DRAW TIMES:

The ovals are drawn until they just about fill the canvas, but one or more corners of the canvas is typically left at the background color, which is defaulted to black.

One reason for not drawing enough ovals to fill all the corners is to assure that we are not drawing more ovals than necessary. Another reason is to help indicate whether the ovals are being drawn with the aspect ratio that was specified.

You can use the 'BackgroundColor' button to set the background to the same color as the 'OuterColor' button. This will generally fill in the entire canvas nicely with 'smooth' coloring --- as seen in an example image below.

The typical draw time for these shaded-oval images in a window of maximum size on a netbook computer (about 1024x600 pixels), with an Intel Atom N450 CPU, is about 2/10 of a second. Since the highest end Intel CPU's are about 10 times faster than the Atom, the images may be completely drawn in far less than 0.1 of a second on a high-end desktop or laptop computer.

CHANGING GRADIENT COLOR(s) and CENTER POINT:

After choosing a color via either the 'CenterColor' or the 'OuterColor' button (OR after a button1-release event on the canvas, to change the 'center point'), this script will automatically do a redraw.

Here is an image to show a redraw after clicking on the upper left corner of the canvas:

And here is an image to show a redraw after using the 'BackgroundColor' button to change the canvas background from black to the same color as the color showing in the 'OuterColor' button:


USES OF THE 'DRAW' BUTTON:

The 'scale' widget on the GUI can be used to set the 'aspect ratio' to be used to redraw the ovals. After setting a new aspect ratio, the user can click on the 'Draw' button to cause a redraw. An aspect ratio of 1.0 causes the ovals to be circular.

The canvas can be re-sized by re-sizing the window. The 'Draw' button can be used after a window re-size, to cause a re-draw that fills the new canvas size.

CAPTURING THE GENERATED IMAGE:

A screen/window capture utility (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG or GIF file, say.

If necessary, an image editor (like 'mtpaint' on Linux) can be used to crop the window capture image. The image could also be down-sized --- say to make a 'bullet' image file or an icon image file.

I used 'gnome-screenshot' and 'mtpaint' to capture the images shown above.

USES FOR THE IMAGES:

The resulting color-gradient-ovals image can be used for

  • Tk GUI button backgrounds
  • icon backgrounds
  • logo backgrounds
  • title areas in web pages
  • borders of (or tiled backgrounds of entire) web pages
  • 'desktop' backgrounds, tiled if need be
  • backgrounds for Tk GUI 'toolchests'
  • your choice

Text or images or drawings could be superimposed on the color-gradient image by using one of this author's other Tk GUI utilities, such as


The 'Help' button on the GUI provides pretty complete and detailed help for using the GUI --- mostly the information of the paragraphs above.

The code

Below, I provide the Tk script code for this 'color-gradient-ovals' 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 new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.

Experimenting with the GUI

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

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

I think that I have used a nice choice of the 'pack' parameters. The label and button and scale widgets stay fixed in size and relative-location as the window is re-sized --- while the canvas area expands/contracts appropriately if the window is re-sized.

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.

---

Note that the 3 'Color' buttons call on a color-selector-GUI script to set the color-gradient and background colors. You can make that color-selector script by cutting-and-pasting the code from the page A non-obfuscated color selector GUI on this site.

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 proc is the 'ReDraw' proc which is used to draw the sequence of oval curves on the canvas using

  • the currently-specified colors
  • the current 'center point'
  • the current 'aspect ratio'
  • the current canvas dimensions.

A proc that may be of some interest is 'update_color_labels'. It is called by the 3 set-color-and-redraw procs

  • 'set_color1'
  • 'set_color2'
  • 'set_background_color'

to update the background and foreground colors on the 3 buttons, as well as to update the text on the 3 color buttons to show the current color in hex RGB color format.

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 posted by teenagers, whose parents would probably prefer that their child had not posted the video (which makes them look like a blemish on the human evolutionary chain .... both parent and child).

 Code for Tk script 'draw_colorGradientOvals_tween2colors_fromSelectedPoint.tk' :

#!/usr/bin/wish -f
##
## SCRIPT: draw_colorGradientOvals_tween2colors_fromSelectedPoint.tk
##
##+###########################################################################
## PURPOSE: This Tk script allows the user to generate a set of colored
##          ovals on a rectangular canvas --- with all the ovals centered
##          at a user-selected point --- with the colors gradiating
##          from one color at the selected-point to a 2nd color at
##          the 'outer' oval.
##
##          After choosing the colors, the user can then click on
##          a Draw button (or use certain button1-release events)
##          to cause this script to draw, on a Tk canvas, a sequence of
##          ovals --- with the ovals gradiating in color from one chosen
##          color to the other.
##
##          The canvas can be re-sized by re-sizing the window.
##          A window re-size causes the ovals to be redrawn using
##          the current parameters --- colors and 'center point'.
##
##  The resulting color-gradient image can be used as a background for
##      - Tk GUI buttons
##      - icons
##      - logo backgrounds
##      - title areas in web pages
##      - borders of (or backgrounds of entire) web pages
##      - 'desktop' backgrounds
##      - backgrounds for Tk GUI 'toolchests'
##      - your choice
##
##+###########################################################################
## GUI DESIGN:
##
##  The GUI includes the following widgets:
##
##  1) Of course, the GUI has a Tk CANVAS widget on which to draw the
##     sequence of color-changing ovals.
##
##  2) Some BUTTONS such as 'Exit', 'Help', and at least 2 'Color'
##     buttons to bring up a separate color-selector GUI.
##
##+#######################################################################
## '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 action
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically with one or more of
##     the procs), if needed.
##
##+#################################
## Some detail of the code structure of this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :
##
##      '.fRbuttons'   - to contain 'Exit', 'Help', 'Color1', 'Color2' buttons
##
##      '.fRcanvas'    - to contain the canvas widget.
##
##      Sub-frames: none
##
##  1b) Pack ALL frames.
##
##  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:
##         - Double-Button1-release on the canvas, to do a re-draw
##
##  4) Define procs:
##
##      'ReDraw'  - to (re)draw the sequence of ovals on the canvas
##                  for the current parameters --- 2 user-selected colors,
##                  a user-selected 'center point', and the current
##                  canvas dimensions.
##
##      'set_color1'
##      'set_color2'
##      'set_background_color'
##
## 'popup_msg_var_scroll' - to show messages to the user, such as
##                          the HELPtext for this utility
##
##  5) Additional GUI initialization:  ReDraw for inital settings of
##                                     the colors and the 'center point'
##                                     and the current canvas dimensions.
##
##+#######################################################################
## 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 settings are changed to
##   old-style).
##+#######################################################################
## MAINTENANCE HISTORY:
## Started by: Blaise Montandon 2013aug12 Started development, on Ubuntu 9.10.
## Changed by: Blaise Montandon 2013aug13 Added the 'update_colors_label'
##                                        proc to color the color buttons.                       
##+########################################################################

##+#######################################################################
## Set WINDOW TITLES and POSITION.
##+#######################################################################

wm title    . "Draw Color-Gradient Ovals - from a user-selected 'center point'"
wm iconname . "ColorOvals"

wm geometry . +15+30


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

tk_setPalette "#e0e0e0"

## Initialize gradient color1.

if {1} {
## White
set COLOR1r 255
set COLOR1g 255
set COLOR1b 255
}

if {0} {
## Magenta
set COLOR1r 255
set COLOR1g 0
set COLOR1b 255
}

set COLOR1hex [format "#%02X%02X%02X" $COLOR1r $COLOR1g $COLOR1b]


## Initialize gradient color2.

if {0} {
## Yellow
set COLOR2r 255
set COLOR2g 255
set COLOR2b 0
}

if {1} {
## Blue
set COLOR2r 0
set COLOR2g 0
set COLOR2b 255
}

set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b]


## Initialize the background color for the canvas.

if {0} {
## Dark gray
set COLORBKGDr 60
set COLORBKGDg 60
set COLORBKGDb 60
}

if {1} {
## Black
set COLORBKGDr 0
set COLORBKGDg 0
set COLORBKGDb 0
}

set COLORBKGDhex \
    [format "#%02X%02X%02X" $COLORBKGDr $COLORBKGDg $COLORBKGDb]

# set entryBKGD     "#f0f0f0"
# set listboxBKGD   "#f0f0f0"
# set radiobuttBKGD "#f0f0f0"
set scaleBKGD "#f0f0f0"


##+########################################################
## Set (temp) FONT NAMES.
##
## Use a VARIABLE-WIDTH font for text on label and
## button widgets.
##
## Use a FIXED-WIDTH font for text in a listbox list and for
## the text in the entry field or a text widget.
##+########################################################

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

font create fontTEMP_SMALL_varwidth \
   -family {comic sans ms} \
   -size -10 \
   -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 -10 \
   -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)
##+###########################################################

set initCanWidthPx 300
set initCanHeightPx 300

set minCanWidthPx 24
set minCanHeightPx 24

# set BDwidthPx_canvas 2
  set BDwidthPx_canvas 0


## BUTTON widget geom settings:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## LABEL widget geom settings:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2


## SCALE geom parameters:

set BDwidthPx_scale 2
set initScaleLengthPx 300
set scaleWidthPx 10


## RADIOBUTTON geom parameters:

# set PADXpx_radbutt 0
# set PADYpx_radbutt 0
# set BDwidthPx_radbutt 2


## CHECKBUTTON geom parameters:

# set PADXpx_chkbutt 0
# set PADYpx_chkbutt 0
# set BDwidthPx_chkbutt 2

## ENTRY widget geom settings:

# set BDwidthPx_entry 2
# set initEntryWidthChars 50


## LISTBOX geom settings:

# set BDwidthPx_listbox 2
# set initListboxWidthChars 50
# set initListboxHeightChars 8


##+######################################################
## Set a MIN-SIZE of the window (roughly).
##
## Set approx MIN WIDTH according to widgets across the
## '.fRbuttons' frame.
##
## Set approx MIN HEIGHT according to the height of the
## vertical 'stack' of frames.
##+######################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
     "Exit  Help  Color1  Color2  BkgdColor"]

## Add some pixels to account for right-left-size of window-manager
## decoration (about 6 pixels) and frame/widget borders for
## at least 4 buttons (4x4 pixels/widget).

set minWinWidthPx [expr {20 + $minWinWidthPx}]


## Get the approx MIN HEIGHT based on
##     2 chars   high for the '.fRbuttons' frame
##    24 pixels  high for the '.fRcanvas' frame
##
## and add about 20 pixels for top-bottom window decoration --
## and about 2x4 pixels for frame/widget borders of the 2 frames.

set charHeightPx [font metrics fontTEMP_fixedwidth -linespace]

set minWinHeightPx [expr {28 + ( 2 * $charHeightPx )} ]

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

wm minsize . $minWinWidthPx $minWinHeightPx

## We allow the window to be resizable and we pack the canvas with
## '-fill both' so that the canvas 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


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

set aRtext(buttonEXIT)  "Exit"
set aRtext(buttonHELP)  "Help"
set aRtext(buttonDRAW)  "Draw"

set aRtext(buttonCOLOR1) "CentrColr"

set aRtext(buttonCOLOR2) "OutrColr"

set aRtext(buttonCOLORBKGD) "BkgdColr"

set aRtext(labelRATIO) "x-to-y Aspect Ratio of
Ovals (0.8 to 2.0):"

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


##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : '.fRbuttons'  '.fRcanvas'
##
##   Sub-frames: none
##+################################################################

## FOR TESTING: (resizing of frames as window is resized)
#  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 .fRcanvas    -relief raised         -bd 2


##+##############################
## PACK the FRAMES. 
##+##############################

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

pack .fRcanvas \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1

##+#######################################################
## The frames are defined and packed.
##+#######################################################
## DEFINE and PACK WIDGETS, frame by frame.
##+#######################################################

##+#######################################################
## In '.fRbuttons' FRAME -
## DEFINE-and-PACK BUTTON widgets
## --- Exit,Help,Color1,Color2,ColorBkgd ---
## and a LABEL and SCALE widget (for aspect ratio of ovals).
##+#######################################################

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.buttDRAW \
   -text "$aRtext(buttonDRAW)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {ReDraw 0}


button .fRbuttons.buttCOLOR1 \
   -text "$aRtext(buttonCOLOR1)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {set_color1}

button .fRbuttons.buttCOLOR2 \
   -text "$aRtext(buttonCOLOR2)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {set_color2}

button .fRbuttons.buttCOLORBKGD \
   -text "$aRtext(buttonCOLORBKGD)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {set_background_color}


label .fRbuttons.labelRATIO \
   -text "$aRtext(labelRATIO)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

## Set this widget var in the GUI initialization section
## at the bottom of this script.
# set aspectRATIOxTOy 2.0

scale .fRbuttons.scaleRATIO \
   -from 0.8 -to 2.0 \
   -resolution 0.1 \
   -length 120 \
   -font fontTEMP_SMALL_varwidth \
   -variable aspectRATIOxTOy \
   -showvalue true \
   -orient horizontal \
   -bd $BDwidthPx_scale \
   -width 10

## Pack the '.fRbuttons' widgets.

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttCOLOR1 \
     .fRbuttons.buttCOLOR2 \
     .fRbuttons.buttCOLORBKGD \
     .fRbuttons.buttDRAW \
     .fRbuttons.labelRATIO \
     .fRbuttons.scaleRATIO \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+########################################################
## In the '.fRcanvas' FRAME -
## DEFINE-and-PACK CANVAS WIDGET.
##+########################################################
## We set highlightthickness & borderwidth of the canvas to
## zero, as suggested on page 558, Chapter 37, 'The Canvas
## Widget', in the 4th edition of the book 'Practical
## Programming in Tcl and Tk'.
##+######################################################

canvas .fRcanvas.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

pack .fRcanvas.can \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1


##+##################################################
## END OF DEFINITION of the GUI widgets.
##+##################################################
## Start of BINDINGS, PROCS, Added-GUI-INIT sections.
##+##################################################

##+#######################################################################
##+#######################################################################
##  BINDINGS SECTION:
##   - For button1-release on the canvas, to redraw the ovals
##     for a new 'center point'.
##+#######################################################################

bind .fRcanvas.can <ButtonRelease-1>  { reset_center_point %x %y ; ReDraw 0 }

# bind .fRbuttons.scaleRATIO <ButtonRelease-1>  { ReDraw 0 }


##+##################################################################
##+##################################################################
## DEFINE PROCS SECTION:
##
##    'reset_center_point' - sets global variables centerXpx,centerYpx
##                           according the point just clicked.
##
##    'ReDraw'  - to draw the sequence of curves on the canvas from the
##                currently-specified colors and the current 'center point'
##                and the current aspectRATIOxTOy and the current canvas
##                dimensions.
##
##   'set_color1'    - called by gradient-color1 button '-command'
##
##   'set_color2'    - called by gradient-color2 button '-command'
##
##   'set_background_color' - called by background color button '-command'
##
##   'update_color_labels'  - called by the 3 color procs to update
##                            colors and text on the 3 color buttons
##
##   'popup_msgVarWithScroll' - used to show messages to the user, such as
##                              the HELPtext for this utility
##
##+#################################################################


##+######################################################################
## proc 'reset_center_point':
##
## PURPOSE: Sets variables centerXpx,centerYpx
##          according the point just clicked.
##
## CALLED BY: pressing the Draw button and, maybe, some button1-release
##            bindings on radiobuttons --- and within the 2 color procs.
##+######################################################################

proc reset_center_point {x y} {

   global centerXpx centerYpx

   set centerXpx $x
   set centerYpx $y

}
## END OF proc 'reset_center_point'


##+######################################################################
## proc 'ReDraw':
##
## PURPOSE: To draw the sequence of ovals on the canvas ---
##          with 'create oval' commands ---
##          from given colors and the current 'center point'
##          and the current aspectRATIOxTOy value and the current canvas
##          dimensions.
##
## CALLED BY: pressing the Draw button and, maybe, some button1-release
##            bindings on the canvas --- and perhaps on the aspectRATIOxTOy
##            scale --- and perhaps within the color1 & color2 procs.
##+######################################################################
## NOTE: The 'x' argument is to avoid an error when the scale '-command'
##       passes a scale value as an argument to the command. This is in
##       case we try using the '-command' option of scale widgets to
##       do the redraws 'dynamically' as a sliderbar is moved.
##+#####################################################################

proc ReDraw {x} {

   global centerXpx centerYpx aspectRATIOxTOy \
         COLOR1r COLOR1g COLOR1b COLOR1hex \
         COLOR2r COLOR2g COLOR2b COLOR2hex \
         COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex

   wm title . "*BUSY* ... Performing redraw at center: $centerXpx $centerYpx"

   ## Clear the canvas.
   .fRcanvas.can delete all

   ## Set the current time, for determining execution
   ## time for building the photo image, below.
   set t0 [clock milliseconds]

   ## Get the current canvas size.
   set curCanWidthPx  [winfo width  .fRcanvas.can]
   set curCanHeightPx [winfo height .fRcanvas.can]

   ## Set a limit for the number of ovals to draw.

   set MAX_Xpx $centerXpx
   set delta [expr {$curCanWidthPx - $centerXpx}]
   if {$delta > $MAX_Xpx} {set MAX_Xpx $delta}
   set MAX_Xpx [expr {int($MAX_Xpx / $aspectRATIOxTOy)}]

   set MAX_Ypx $centerYpx
   set delta [expr {$curCanHeightPx - $centerYpx}]
   if {$delta > $MAX_Ypx} {set MAX_Ypx $delta}
   set MAX_Ypx [expr {int($MAX_Ypx * $aspectRATIOxTOy)}]

   set MAXovals $MAX_Xpx
   if {$MAX_Ypx > $MAXovals} {set MAXovals $MAX_Ypx}

   ## FOR TESTING:
   #   puts "ReDraw > MAXovals: $MAXovals"
   #   puts "curCanWidthPx : $curCanWidthPx"
   #   puts "curCanHeightPx: $curCanHeightPx"

   ## Set the RGB ranges for the 2 selected colors.
   set rRange [expr {$COLOR2r - double($COLOR1r)}]
   set gRange [expr {$COLOR2g - double($COLOR1g)}]
   set bRange [expr {$COLOR2b - double($COLOR1b)}]

   ## Set some RGB ratios to use for color interpolation.
   set MAXovals [expr {double($MAXovals)}]
   set rRatio [expr {$rRange / $MAXovals}]
   set gRatio [expr {$gRange / $MAXovals}]
   set bRatio [expr {$bRange / $MAXovals}]

   ## Let us make sure $aspectRATIOxTOy is float, not integer.

   # set aspectRATIOxTOy [expr {double($aspectRATIOxTOy)}]

   ## Start the oval-drawing loop.

   for {set i 0} {$i < $MAXovals} {incr i} {

      ## Interpolate to get a color for this oval.
      set R255 [expr int( $COLOR1r + ($rRatio * $i) )]
      set G255 [expr int( $COLOR1g + ($gRatio * $i) )]
      set B255 [expr int( $COLOR1b + ($bRatio * $i) )]
      set tempCOLORhex [format "#%02X%02X%02X" $R255 $G255 $B255]

      ## Set the corners of the box to contain the oval.
      set ULXpx [expr {int($centerXpx - ($i * $aspectRATIOxTOy))}]
      set ULYpx [expr {int($centerYpx - ($i / $aspectRATIOxTOy))}]
      set LRXpx [expr {int($centerXpx + ($i * $aspectRATIOxTOy))}]
      set LRYpx [expr {int($centerYpx + ($i / $aspectRATIOxTOy))}]

      .fRcanvas.can create oval \
         $ULXpx $ULYpx $LRXpx $LRYpx \
         -outline $tempCOLORhex -width 3

      # -width 2
      # -width 1

      ## FOR TESTING: (To see each oval being drawn.)
      #  update

   }
   ## END OF oval-drawing loop

   ## Change the title of the window to show execution time.
   wm title . "DONE.  [expr [clock milliseconds]-$t0] milliseconds elapsed."

}
## END OF PROC  'ReDraw'


##+#####################################################################
## proc 'set_color1'
##+##################################################################### 
## PURPOSE:
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set a 'fill' color.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR1  button
##+#####################################################################

proc set_color1 {} {

   global COLOR1r COLOR1g COLOR1b COLOR1hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR1r: $COLOR1r"
   #    puts "COLOR1g: $COLOR1g"
   #    puts "COLOR1b: $COLOR1b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR1r $COLOR1g $COLOR1b]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLOR1hex "#$hexRGB"
   set COLOR1r $r255
   set COLOR1g $g255
   set COLOR1b $b255

   ## Update the colors on the color buttons.
   update_colors_label

   ## Redraw the geometry in the new interior color.

   ReDraw 0

}
## END OF proc 'set_color1'


##+#####################################################################
## proc 'set_color2'
##+##################################################################### 
## PURPOSE:
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set an 'outline' color.
##
## Arguments: none
##
## CALLED BY: .fRbuttons.buttCOLOR2  button
##+#####################################################################

proc set_color2 {} {

   global COLOR2r COLOR2g COLOR2b COLOR2hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR2r: $COLOR2r"
   #    puts "COLOR2g: $COLOR2g"
   #    puts "COLOR2b: $COLOR2b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR2r $COLOR2g $COLOR2b]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLOR2hex "#$hexRGB"
   set COLOR2r $r255
   set COLOR2g $g255
   set COLOR2b $b255

   ## Update the colors on the color buttons.
   update_colors_label

   ## Redraw the geometry in the new outline color.

   ReDraw 0

}
## END OF proc 'set_color2'


##+#####################################################################
## proc 'set_background_color'
##+##################################################################### 
## PURPOSE:
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set the color of the canvas ---
##   on which all the ovals lie.
##
## Arguments: none
##
## CALLED BY: .fRbuttons.buttCOLORBKGD  button
##+#####################################################################

proc set_background_color {} {

   global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLORBKGDr: $COLORBKGDr"
   #    puts "COLORBKGDg: $COLORBKGDb"
   #    puts "COLORBKGDb: $COLORBKGDb"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLORBKGDr $COLORBKGDg $COLORBKGDb]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLORBKGDhex "#$hexRGB"
   set COLORBKGDr $r255
   set COLORBKGDg $g255
   set COLORBKGDb $b255

   ## Update the colors on the color buttons.
   update_colors_label

   ## Set the color of the canvas.

   .fRcanvas.can config -bg $COLORBKGDhex

}
## END OF proc 'set_background_color'


##+#####################################################################
## proc 'update_colors_label'
##+##################################################################### 
## PURPOSE:
##   This procedure is invoked to update the text in a COLORS
##   label widget, to show hex values of current color1, color2,
##   and background-color settings.
##
##   This proc also sets the background color of each of those 3 buttons
##   to its current color --- and sets foreground color to a
##   suitable black or white color, so that the label text is readable.
##
## Arguments: global color vars
##
## CALLED BY:  3 colors procs:
##            'set_object_color1'
##            'set_object_color2'
##            'set_background_color'
##             and the additional-GUI-initialization section at
##             the bottom of this script.
##+#####################################################################

proc update_colors_label {} {

   global aRtext COLOR1hex COLOR2hex COLORBKGDhex \
      COLOR1r COLOR1g COLOR1b \
      COLOR2r COLOR2g COLOR2b \
      COLORBKGDr COLORBKGDg COLORBKGDb

   ## Set background color on the COLOR1 button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLOR1 configure -bg $COLOR1hex
   .fRbuttons.buttCOLOR1 configure -text "$aRtext(buttonCOLOR1)
$COLOR1hex"

   set sumCOLOR1 [expr {$COLOR1r + $COLOR1g + $COLOR1b}]
   if {$sumCOLOR1 > 300} {
      .fRbuttons.buttCOLOR1 configure -fg #000000
   } else {
      .fRbuttons.buttCOLOR1 configure -fg #f0f0f0
   }


   ## Set background color on the COLOR2 button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLOR2 configure -bg $COLOR2hex
   .fRbuttons.buttCOLOR2 configure -text "$aRtext(buttonCOLOR2)
$COLOR2hex"

   set sumCOLOR2 [expr {$COLOR2r + $COLOR2g + $COLOR2b}]
   if {$sumCOLOR2 > 300} {
      .fRbuttons.buttCOLOR2 configure -fg #000000
   } else {
      .fRbuttons.buttCOLOR2 configure -fg #f0f0f0
   }


   ## Set background color on the COLORBKGD button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLORBKGD configure -bg $COLORBKGDhex
   .fRbuttons.buttCOLORBKGD configure -text "$aRtext(buttonCOLORBKGD)
$COLORBKGDhex"

   set sumCOLORBKGD [expr {$COLORBKGDr + $COLORBKGDg + $COLORBKGDb}]
   if {$sumCOLORBKGD > 300} {
      .fRbuttons.buttCOLORBKGD configure -fg #000000
   } else {
      .fRbuttons.buttCOLORBKGD configure -fg #f0f0f0
   }

}
## END OF proc 'update_colors_label'


##+#############################################################
## proc redraw_if_canvas_resized
##
## CALLED BY: a <Configure> binding at the bottom of this script
##+#############################################################

proc redraw_if_canvas_resized {} {

   global  PREVcanWidthPx PREVcanHeightPx

   set CURcanWidthPx  [winfo width  .fRcanvas.can]
   set CURcanHeightPx [winfo height .fRcanvas.can]

   if { $CURcanWidthPx  != $PREVcanWidthPx ||
        $CURcanHeightPx != $PREVcanHeightPx} {
      ReDraw 0
      set PREVcanWidthPx  $CURcanWidthPx
      set PREVcanHeightPx $CURcanHeightPx
   }

}
## END OF proc 'redraw_if_canvas_resized'


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

   text $toplevName.text \
      -wrap none \
      -font fontTEMP_varwidth \
      -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"

   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

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


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

set HELPtext "\
**********  HELP for the Color-Gradient-Ovals Utility *****************

   This Tk script QUICKLY generates a sequence of colored ovals
   on a rectangular canvas --- with all the ovals centered at a
   user-selected point --- and with the colors gradiating from one
   color at the selected 'center point' to a 2nd color at the 'outer'
   oval. Simply click on the canvas to establish a new 'center point'.

   The sequence of ovals is intended to yield a nicely color shaded
   image, with a highlight in any portion of the image. Some possible
   applications are listed below.

   A 'scale' widget on the GUI can be used to set the 'aspect ratio'
   to be used to redraw the ovals. After setting a new aspect ratio,
   the user can click on the 'Draw' button to cause a redraw. An aspect
   ratio of 1.0 causes the ovals to be circular.

   After choosing a color via either the 'CenterColor' or the 'OuterColor'
   button (OR after a button1-release event on the canvas, to change the
   'center point'), this script will automatically do a redraw.

   The canvas can be re-sized by re-sizing the window. The 'Draw' button
   can be used after a window re-size, to cause a re-draw that fills
   the new canvas size.

EXTENT OF THE OVALS and DRAW TIMES:

   The ovals are drawn until they just about fill the canvas, but one or
   more corners of the canvas is typically left at the background color,
   which is defaulted to black. You can use the 'BackgroundColor' button
   to set the background to the same color as the 'OuterColor' button.
   This will generally fill in the entire canvas nicely with 'smooth'
   coloring.

   (One reason for not drawing enough ovals to fill all the corners is to
    assure that we are not drawing more ovals than necessary. Another
    reason is to help indicate whether the ovals are being drawn with
    the aspect ratio that was specified.)

   The typical draw time for these shaded-oval images in a window of maximum
   size on a netbook computer (about 1024x600 pixels), with an Intel Atom
   N450 CPU, is about 2/10 of a second. Since the highest end Intel CPU's
   are about 10 times faster than that, the images may be drawn in far less
   than 0.1 of a second on a high-end desktop or laptop computer.

USES FOR THE IMAGES:

   The resulting color-gradient image can be used for
      - Tk GUI button backgrounds
      - icon backgrounds
      - logo backgrounds
      - title areas in web pages
      - borders of (or tiled backgrounds of entire) web pages
      - 'desktop' backgrounds, tiled if need be
      - backgrounds for Tk GUI 'toolchests'
      - your choice

   Text or images or drawings could be superimposed on the color-gradient
   image by using one of this author's other Tk GUI utilities, such as

   http://wiki.tcl.tk/37228 -
   A GUI for making 'Title Blocks' ... with text, fonts, colors, images

   http://wiki.tcl.tk/37351 -
   wheeeDiagram --- some bells and whistles

   http://wiki.tcl.tk/37120 -
   A 'Sketch On' GUI ... for drawing on an image or colored background
 

CAPTURING THE GENERATED IMAGE:

   A screen/window capture utility (like 'gnome-screenshot' on Linux)
   can be used to capture the GUI image in a PNG or GIF file, say.

   If necessary, an image editor (like 'mtpaint' on Linux)
   can be used to crop the window capture image.  The image
   could also be down-sized --- say to make a 'bullet' image
   file or an icon image file.

METHOD OF DRAWING:

   This script uses Tk drawing commands (on the Tk canvas) of the
   form:

   .canvas create oval x1 y1 x2 y2 -outline <hexCOLOR> -width 3

   The 'hexCOLOR' is interpolated from the 'CenterColor' and the
   'OuterColor'. The width of 3 pixels is used to avoid occasional
   pixels not being covered by the ovals, resulting in pixels of
   the background color showing through the sequence of ovals that
   have been drawn on the canvas. You can try other widths of 1 and 2
   to see the effect. If you like that option, you could add another
   widget to the GUI to allow for setting the width of the ovals.
"

##+################################################################
## Additional GUI INITIALIZATION:
##+################################################################

set aspectRATIOxTOy 2.0


## We need this statement because ReDraw (below) does not
## (re)set the background/canvas color.
.fRcanvas.can config -bg $COLORBKGDhex

## Initialize/update the colors on the color buttons.
update_colors_label

## Need 'update' here to set the size of the canvas,
## because 'ReDraw' uses 'winfo' to get the width and
## height of the canvas.
update

##+##########################################################
## When this script drops into the Tk event-handling loop,
## the 'bind . <Configure> "redraw_if_canvas_resized"'
## command below causes redraws whenever the canvas is resized.
##
## (Unfortunately, the <Configure> binding seems to cause more
## re-draws than we really need. We just want a re-draw when
## the mouse-button is released after re-sizing the window,
## NOT repeatedly while the window border is being moved.
##
## EVEN MORE unfortunately, the <Configure> binding seems to
## cause the 'scale' widget to go wild and auto-advance,
## accompanied by a barrage of re-draws.)
##
## We initialize the PREVcanWidthPx and PREVcanHeightPx
## vars that are used in the 'redraw_if_canvas_resized' proc.
## Those canvas dimensions were intended to be used to try to
## make sure that when a <Configure> event occurs, a redraw is
## at least restricted to those events that actually result
## in a change in the size of the canvas.
##+##########################################################

set PREVcanWidthPx  [winfo width  .fRcanvas.can]
set PREVcanHeightPx [winfo height .fRcanvas.can]

## Initialize the 'center point' of the ovals.
## (We could start off-center.)
set centerXpx [expr {int($PREVcanWidthPx / 2)}]
set centerYpx [expr {int($PREVcanHeightPx / 2)}]

ReDraw 0

## DEACTIVATED. Seems to cause the 'scale' to auto-advance
##              and cause tons of redraws.
# bind . <Configure> "redraw_if_canvas_resized"


SOME POTENTIAL ENHANCEMENTS:

This script uses Tk drawing commands (on the Tk canvas) of the form:
   .canvas create oval x1 y1 x2 y2 -outline <hexCOLOR> -width 3

The 'hexCOLOR' is interpolated from the 'CenterColor' and the 'OuterColor'.

The width of 3 pixels is used to avoid occasional pixels not being covered by the ovals, resulting in pixels of the background color showing through the sequence of ovals that have been drawn on the canvas.

You can try other widths, like 1 and 2, to see the effect. If you like that option, you could add another widget to the GUI to allow for setting the line-width of the ovals.

As for myself, I plan to make a few more GUI's that use lines, other than horizontal or vertical straight-lines, to make color gradient images --- using, for example

  • functions of the form y=f(x) or x=f(y) --- or x=g(t) and y=h(t)
  • radial lines
  • spline curves.

Someday.

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.