Updated 2014-06-07 20:22:09 by uniquename

uniquename - 2013aug12

I was reading a math book the other day --- 'A History of Pi' by Petr Beckmann. There was a presentation of a formula (and geometry) for estimating the length of an arc on a circle by getting the length of a straight line segment, constructed nearby.

This got me to thinking about converting angles from one unit to another --- radians or degrees to distances.

It then hit me that my color-selector GUI (with 3 'slider buttons') --- whose code I contributed at A non-obfuscated color selector GUI --- could easily be converted into an angle-converter utility.

And the 'color swatch' on that utility (which was done with a 'frame' widget) could be changed to a 'canvas' widget, on which a representation of the current angle could be drawn.

There would need to be a big difference between the 'color selector' GUI and the 'angle converter' GUI --- namely, the 3 RGB sliders on the 'color selector' GUI needed to move independently of each other.

For the 'angle converter' GUI, the 3 sliders would need to move together. But the Tk 'scale' widget handles that nicely. In Tk, a variable is associated with each 'slider', and any time the value of that variable changes, the slider moves to reflect the changed value.

I just needed to make sure that when any of the 3 variables (containing the value of an angle in 3 different units) changed, the other 2 variables were changed correspondingly.

So I set to work converting the code of the color-selector GUI to an angle-converter GUI, and I ended up with the GUI seen in the following image.

This image does not do justice to the immediacy of the changes. Whenever ONE of the 'sliders' is moved:

  • the other TWO sliders move
  • the angle depicted in the circle on the canvas changes
  • the 3 text areas change to present the current 3 values for the angle (available for copy-paste into another window on the desktop)

The 'Help' button on the GUI provides pretty complete and detailed help for using the GUI.

Here is most of the Help:

** HELP for this 'tkAngle-Convert-Select' GUI utility **

** for angles in radians, degrees, or percent-of-circumference **

This Tk script provides a GUI for showing angles between zero and 360 degrees in three different units:

  • degrees
  • radians
  • percents of the circumference of a circle.

These 3 different units are shown (and selected for conversion to the other 2 units) via 3 'sliders' of 3 Tk 'scale' widgets.

These 3 different (corresponding) numbers are also shown in 3 'text' widgets, a few characters wide --- from which a user can copy-and-paste the numbers from this GUI window to some other window, such as a text-editor window.

For any 'slider' change, this GUI script IMMEDIATELY updates the location of the other 2 'sliders' --- and the current angle is depicted on a circle drawn in a 'canvas' wdiget in a corner of the GUI window (covering about 20% of the window).

The 3 text widgets, that show the angle in the 3 different units, are also IMMEDIATELY updated whenever any one of the 3 scale widget 'sliders' is changed.

By clicking on a 'UseIt' button on the GUI, this script returns a string containing the current angle (in the 3 different units) to a calling script/application.

INITIALIZING THE ANGLE:

This script will accept an angle (in degrees) which is used to initialize the

  • setting of the 3 'sliders'
  • the numbers shown in the 3 small text widgets
  • the display of an angle in a circle on the canvas widget.

The angle can be passed to the script as an argument --- OR, if some 'catch' statements in the script are de-commented --- the angle can be passed as an environment variable.

FINE ADJUSTMENTS OF THE SCALES:

It is typically not obvious (esp. to a user who has not used the Tk scale widget before) that one can easily advance the 'slider' one resolution-unit at a time.

You can mouse-click ON EITHER SIDE OF the slider 'button' to advance the slider one resolution-unit per click. Simply click repeatedly to adjust the slider to a specific value.

AND, by clicking on either side and HOLDING DOWN, the slider will rapidly advance one resolution-unit at a time UNTIL the mouse-button is RELEASED.

SOME USES of this utility:
   1) Useful for helping math-science students get a visual
      'feel' for the relation between radians and degrees and
      the distance around the circumference of a circle.

   2) Also could be useful for programmers (example: Tcl-Tk programmers)
      for determining angles (example: radians) to be used in
      various Tcl-Tk apps --- example: Tk apps doing 'create arc'
      commands on a Tk 'canvas'.

   3) IN A SHELL SCRIPT OR ANOTHER TK SCRIPT, this Tk script can
      ACT AS AN ANGLE SELECTOR by passing the current angle (in
      the 3 different units) to 'stdout', when the 'UseIt' button
      is clicked.
         Example output string:  180.0  3.14159  0.5

      (When the 'Cancel' button is clicked, the GUI is closed
       WITHOUT sending a text string to 'stdout'.)

Note: The user can set up this script as an icon on a desktop so that the GUI can be started up by a click (or two) on the icon.

The code

Below, I provide the Tk script code for this 'tkAngle-Convert-Select' 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.

In this particular GUI, like for the 'color selector' GUI on which it was based, I have chosen to make the window stay at the same size that it takes when it is initially 'packed'.

However, one could comment the statement
   wm resizable . 0 0

and activate the statement
   wm minsize . $minWinWidthPx $minWinHeightPx

and the statements that set the min-width and min-height values.

I have used some nice choices for the 'pack' parameters. The labels and buttons and scales are set to stay fixed in size and relative-location as the window is re-sized. But it might be nice to make the canvas increase in size if the window is increased in size.

Currently, the calculation for putting the circle and filled-angle on the canvas may not scale properly as window size is changed.

If you want to make 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 --- and adjust the calculations for drawing the circle and filled-angle, if necessary.

___

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

  • angle_update - called once initially, and then whenever a slider is moved
  • draw_circle - called once to draw the circle
  • redraw_angle - called by 'angle_update'
  • put_vars - called when the 'UseIt' button is clicked
  • popup_msgVarWithScroll - called when the 'Help' button is clicked

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

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of cats-looking-bored --- and then going crazy.

 Code for Tk script 'tkAngleConvertSelect_radians-degrees-percents.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' there
##   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:   tkAngleConvertSelect_radians-degrees-percents.tk           
##
##+#######################################################################
## PURPOSE:  This TkGUI script provides a GUI for showing angles between
##           0 and 360 degrees in three different units:
##             - degrees
##             - radians
##             - percents of the circumference of a circle.
##
##           These 3 different units are shown (and selected for conversion)
##           via 3 'sliders' of 3 Tk 'scale' widgets. 
##
##           These 3 different (corresponding) numbers are also shown
##           in 3 'text' widgets, a few characters wide --- from which
##           a user can copy-and-paste the numbers from this GUI window 
##           to some other window. 
##
##           For any 'slider' change, this GUI script IMMEDIATELY
##           updates the location of the other 2 'sliders' --- and
##           the current angle is depicted on a circle drawn 
##           in a 'canvas' wdiget in a corner of the GUI
##           window (covering about 20% of the window).
##
##           The 3 text widgets showing the angle in the 3 different units,
##           are also IMMEDIATELY updated whenever any one of the 3
##           scale widget 'sliders' is changed.
##
##           By clicking on a 'UseIt' button on the GUI, this script
##           returns a string containing the current angle (in the
##           3 different units) to a calling script/application.
##
##           This script sill accept an angle (in degrees) which is
##           used to initialize the
##             - setting of the 3 'sliders'
##             - the numbers shown in the 3 small text widgets
##             - the display of an angle in a circle on the canvas widget.
##
## SOME USES:
##
##       1)  Useful for helping math-science students get a visual
##           'feel' for the relation between radians and degrees and
##           the distance around the circumference of a circle.
##
##       2)  Also could be useful for programmers (ex: Tcl-Tk programmers)
##           for determining angles (example: in radians) to be used in
##           various Tcl-Tk apps --- example: Tk apps doing 'create arc'
##           commands on a Tk 'canvas'.
##
##       3)  IN A SHELL SCRIPT OR ANOTHER TK SCRIPT, this Tk script can
##           ACT AS AN ANGLE SELECTOR by passing the current angle (in
##           the 3 different units) to stdout, when the OK/UseIt button
##           is clicked.
##           Example output string:  180.0  3.14159  0.5
##
##+########################################################################
## '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 : 'fRsliders', 'fRdepict' , 'fRbuttons'
##        Sub-frames: 'fRsliders.fRdeg', 'fRsliders.fRrad', 'fRsliders.fRpcnt'
##
##        'fRsliders' and 'fRdepict' are to be on the top of the GUI,
##         on the left and right, respectively.
##        'fRbuttons' is to be on the bottom of the GUI.
##
##  1b) Pack ALL frames.
##
##  2) Define & pack all widgets in the frames -- basically going through
##     frames & their interiors in top-to-bottom and/or left-to-right order:
##
##      'fRsliders'    - to contain 3 sliders - 'scale' wdigets.
##
##      'fRdepict'     - to contain a canvas widget --- on which a circle
##                       is drawn; to depict the current angle.
##
##      'fRbuttons'     - to contain several buttons and label & text widgets
##                       to display the current percent-of-circumference
##                       [0 to 100] and radians [ 0 to  6.28... ] and
##                       degrees [0 to 360].
##
##  3) Define BINDINGS:  none currently
##
##  4) Define PROCS:
##     -  'angle_update'         - for '-command' options on 3 RGB scales
##     -  'draw_circle'
##     -  'draw_angle'
##     -  'put_vars'               - for UseIt button
##     -  'popup_msgVarWithScroll' - for 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:
##             This Tk script is based on a sample Tk script found at
##             http://www.sci.usq.edu.au/~devoil/66309/tut2.html in 1999.
##------------------------------------------------------------------------
## Created by: Blaise Montandon 2013aug09 Started on Ubuntu 9.10, based
##                                        on the color-selector script
##                                        of the FE (Freedom Environment)
##                                        subsystems. Ref: www.freedomenv.com   
## Changed by: Blaise Montandon 20.......
##+############################################################################

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

wm title . \
   "tkAngle-Convert-Select -  radians, degrees, percents-of-circumference-of-circle"

wm iconname . "AngleSelect"

# catch { wm title    . "$env(FE_WIN_TITLE)" }
# catch { wm iconname . "$env(FE_ICON_TITLE)" }


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

wm geometry . +15+30

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


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

if {0} {
##  Grayish palette 
set Rpal255 210
set Gpal255 210
set Bpal255 210
}

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

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

if {1} {
##  Redish palette 
set Rpal255 255
set Gpal255 180
set Bpal255 180
}

set hexCOLORpal [format "#%02X%02X%02X" $Rpal255 $Gpal255 $Bpal255]

tk_setPalette $hexCOLORpal


## Set color background for some widgets.

# set radbuttBKGD "#c0c0c0"
# set chkbuttBKGD "#c0c0c0"
# set listboxBKGD "#f0f0f0"
# set entryBKGD   "#f0f0f0"
set textBKGD    "#f0f0f0"


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

set FONTsize 14
set FONT_SMALLsize 12

## For variable width:

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

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


## For fixed width:

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


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


##+#####################################################################
## DEFINE (temporary) 'font create' NAMES to be used
## in '-font' widget specs below.
##+#####################################################################

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_entry   $FONT_fixedwidth
# eval font create fontTEMP_listbox $FONT_fixedwidth
# eval font create fontTEMP_msg     $FONT_fixedwidth

eval font create fontTEMP_text    $FONT_fixedwidth

eval font create fontTEMP_varwidth    $FONT_varwidth
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_scale   $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_msg     $FONT_SMALL_fixedwidth

# eval font create fontTEMP_SMALL_text    $FONT_SMALL_fixedwidth

# eval font create fontTEMP_SMALL_varwidth    $FONT_SMALL_varwidth
# eval font create fontTEMP_SMALL_fixedwidth  $FONT_SMALL_fixedwidth


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

## For BUTTON widgets:
   
set fePADY_button 0
set fePADX_button 0
set feBDwidth_button 2


## For LABEL widgets:

set PADXpx_label 0
set PADYpx_label 0
# set feBDwidth_label 2
set BDwidthPx_label 2

## SCALE geom parameters:

set BDwidthPx_scale 2
set initScaleLengthPx 300
set scaleWidthPx 10


## CANVAS geom parms:

set initCanWidthPx  200
set initCanHeightPx 200
set minCanHeightPx 24
# set BDwidthPx_canvas 2
set BDwidthPx_canvas 0


## For TEXT widgets:

set feBDwidth_text 2


## For ENTRY widgets:

# set feBDwidth_entry 2


## For LISTBOX widgets:

# set feBDwidth_listbox 2


## For MESSAGE widgets:

# set feBDwidth_msg 2



##+###################################################################
## Set a MINSIZE of the window (roughly). (OR fix the window size.)
##
## For WIDTH, allow for a minwidth of the '.fRbuttons' frame:
##            about 5 buttons (Exit,Clear,Fill,Outline,ColorBkgnd), and
##            NOT a label widget showing current color info.
##
## For HEIGHT, allow
##             2 chars  high for the '.fRbuttons' frame
##             1 char   high for the '.fRobjects' frame
##             1 char   high for the '.fRobjopts' frame
##             1 char   high for the '.fRmodes'   frame (if implemented)
##             1 char   high for the '.fRmodeopts' frame (if implemented),
##            24 pixels high for the '.fRcanvas' frame.
##+#######################################################################
## We allow the window to be resizable and we pack the canvas with
## '-fill both -expand 1' so that the canvas can be enlarged by
## enlarging the window.

# set minWinWidthPx [font measure fontTEMP_varwidth \
#    " UseIt  Cancel  Help  Percent: 0.500  Degrees: 180  Radians: 3.14159"]

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

# set minWinWidthPx [expr {40 + $minWinWidthPx}]


## MIN HEIGHT ---
##             6 chars high for the '.fRsliders' frame
##             1 char  high for the '.fRbuttons' frame

# set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

# set minWinHeightPx [expr {7 * $CharHeightPx}]

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

# set minWinHeightPx [expr {44 + $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


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

set aRtext(labelSCALEdeg)  "Degrees:"
set aRtext(labelSCALErad)  "Radians:"
set aRtext(labelSCALEpcnt) "Percent:"


## For '.fRbuttons' frame:

set aRtext(buttonUSEIT)  "UseIt"
set aRtext(buttonCANCEL) "Cancel"
set aRtext(buttonHELP)   "Help"

set aRtext(labelTEXTdeg)  "  Degrees:"
set aRtext(labelTEXTrad)  "  Radians:"
set aRtext(labelTEXTpcnt) "  Percent-of-circle-circumference:"


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


##+########################################################################
##
##  GET INPUT PARM VALUE -- 
##  i.e. set ANGdeg from
##
##         1) an argument passed to this script
##      OR 
##         2)  environment var ANG_DEG
##
##+########################################################################
##+########################################################################
##  Example argc/argv processing:
##
## if {$argc == 0} {
##    set VARtext "$env(CONFIRM_TEXT)"   
## } else {
##    set VARtext [lindex $argv 0]
## }
##+########################################################################

set twopi [expr {8.0 * atan(1.0)}]

if {$argc == 1} {
   set ANGdeg [lindex $argv 0]
} else {
   set ANGdeg 0
   catch { set ANGdeg "$env(ANG_DEG)" }
}

set ANGpcnt [expr {$ANGdeg / 3.60}]
set ANGrad  [expr {($ANGpcnt/100.0) * $twopi}]


##+####################################################################
## DEFINE *ALL* THE FRAMES:
##   TOP-LEVEL FRAMES:
##     - 'fRsliders'  - to contain 3 sliderbars.
##     - 'fRdepict'   - to contain only its background; to display
##                      the color specified by the sliderbar settings.
##     - 'fRbuttons'  - to contain several buttons (UseIt,Cancel,etc), 
##                      as well as 2 label & 2 text widgets to
##                      display the current per-cent and 
##                      hex RGB-values of the color.
##
##  'fRsliders' & 'fRdepict' are packed on left & right.
##
##   SUB-FRAMES:
##     - 'fRsliders.fRdeg'     (to contain 1 label, 1 sliderbar)
##     - 'fRsliders.fRrad'     (to contain 1 label, 1 sliderbar)
##     - 'fRsliders.fRpcnt'    (to contain 1 label, 1 sliderbar)
##+####################################################################

## FOR TESTING:  (like expansion of frames, during window expansion)

# set feRELIEF_frame raised
# set feBDwidth_frame 2

set feRELIEF_frame flat
set feBDwidth_frame 0


frame .fRsliders  -relief $feRELIEF_frame  -borderwidth $feBDwidth_frame

frame .fRdepict   -relief raised           -borderwidth 2

frame .fRbuttons  -relief $feRELIEF_frame  -borderwidth $feBDwidth_frame


frame .fRsliders.fRdeg  -relief $feRELIEF_frame  -borderwidth $feBDwidth_frame

frame .fRsliders.fRrad  -relief $feRELIEF_frame  -borderwidth $feBDwidth_frame

frame .fRsliders.fRpcnt -relief $feRELIEF_frame  -borderwidth $feBDwidth_frame


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

pack  .fRbuttons \
   -side bottom \
   -anchor w \
   -fill x \
   -expand 0

##+##########################################
## NOTE:
## We need to pack the 'bottom' 'fRbuttons'
## frame before the 'fRsliders' & 'fRdepict'
## frames -- otherwise the 'bottom'
## frame comes out in the lower-right
## of the window, instead of below
## 'fRsliders' & 'fRdepict'.
##
## Alternatively, we could define an additional
## frame to contain the 'fRsliders' & 'fRdepcit'
## frames.
##+##########################################

pack .fRsliders \
   -side left \
   -anchor w \
   -fill both \
   -expand 1

pack .fRdepict \
   -side left \
   -anchor w \
   -fill both \
   -expand 1

pack .fRsliders.fRdeg \
     .fRsliders.fRrad \
     .fRsliders.fRpcnt \
   -side top \
   -anchor w \
   -fill x \
   -expand 1


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

##+########################################################
## IN THE 'fRsliders' frame -- DEFINE 3 LABEL and
## 3 SCALE widgets.  THEN PACK THEM.
##+########################################################

## DEGREES:

label .fRsliders.fRdeg.label \
   -text "$aRtext(labelSCALEdeg)" \
   -font fontTEMP_label \
   -width 8 \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

scale .fRsliders.fRdeg.scale \
   -orient horizontal \
   -from 0.0 -to 360.0 \
   -resolution 0.1 \
   -digits 4 \
   -length 350px \
   -variable ANGdeg \
   -font fontTEMP_scale \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -width 10px

##   -command {angle_update}

.fRsliders.fRdeg.scale set $ANGdeg


## RADIANS:

label .fRsliders.fRrad.label \
   -text "$aRtext(labelSCALErad)" \
   -font fontTEMP_label \
   -width 8 \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

scale .fRsliders.fRrad.scale \
   -orient horizontal \
   -from 0.0 -to 6.283 \
   -resolution 0.01 \
   -digits 4 \
   -length 350px \
   -variable ANGrad \
   -font fontTEMP_scale \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -width 10px

##   -command {angle_update}

.fRsliders.fRrad.scale set $ANGrad


## PERCENT-OF-CIRCUMFERENCE:

label .fRsliders.fRpcnt.label \
   -text "$aRtext(labelSCALEpcnt)" \
   -font fontTEMP_label \
   -width 8 \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

scale .fRsliders.fRpcnt.scale \
   -orient horizontal \
   -from 0.0 -to 100.0 \
   -resolution 0.1 \
   -digits 4 \
   -length 350px \
   -variable ANGpcnt \
   -font fontTEMP_scale \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -width 10px

##   -command {angle_update}

.fRsliders.fRpcnt.scale set $ANGpcnt


##+#################################################
## PACK 3 LABELS & 3 SCALES IN 'fRsliders' FRAME.
##+#################################################

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

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

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

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

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

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


##+######################################################
## In the '.fRdepict' frame -
## DEFINE-and-PACK 1 CANVAS widget.
##+######################################################
## We set '-highlightthickness' and '-borderwidth' to
## zero, to avoid covering some of the viewable area
## of the canvas, as suggested on page 558 of the 4th
## edition of 'Practical Programming with Tcl and Tk'.
##+###################################################

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

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



##+########################################################
## IN THE 'fRbuttons' frame -- DEFINE several BUTTONS ---
## 1 'UseIt' BUTTON, 1 'Cancel' BUTTON,
## 1 'Help' BUTTON, and
## 3 TEXT WIDGET (rather than a label or message widget,
## so that it is possible to paste the text values
## to another window), each with a label widget.
##  THEN PACK THE WIDGETS.
##+########################################################

button .fRbuttons.buttUSEIT \
   -text "$aRtext(buttonUSEIT)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {put_vars}
 
button .fRbuttons.buttCANCEL \
   -text "$aRtext(buttonCANCEL)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {exit}

button .fRbuttons.buttHELP \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_button \
   -padx $fePADX_button \
   -pady $fePADY_button \
   -relief raised \
   -bd $feBDwidth_button \
   -command {popup_msgVarWithScroll .topHelp "$HELPtext"}


## Provide label-and-text widgets for the angle values
## in several different units.

set widthChars 10
set widthFiller "          "

## DEGREES:

label .fRbuttons.labTEXTdeg \
   -text "$aRtext(labelTEXTdeg)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

text .fRbuttons.txtANGdeg \
   -relief raised \
   -borderwidth $feBDwidth_text \
   -height 1 \
   -width $widthChars \
   -wrap none \
   -font fontTEMP_text


## RADIANS:

label .fRbuttons.labTEXTrad \
   -text "$aRtext(labelTEXTrad)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

text .fRbuttons.txtANGrad \
   -relief raised \
   -borderwidth $feBDwidth_text \
   -height 1 \
   -width $widthChars \
   -wrap none \
   -font fontTEMP_text


## PERCENT:

label .fRbuttons.labTEXTpcnt \
   -text "$aRtext(labelTEXTpcnt)" \
   -font fontTEMP_label \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

text .fRbuttons.txtANGpcnt \
   -relief raised \
   -borderwidth $feBDwidth_text \
   -height 1 \
   -width $widthChars \
   -wrap none \
   -font fontTEMP_text


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

pack .fRbuttons.buttUSEIT \
     .fRbuttons.buttCANCEL \
     .fRbuttons.buttHELP \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


pack .fRbuttons.txtANGpcnt \
     .fRbuttons.labTEXTpcnt \
     .fRbuttons.txtANGrad \
     .fRbuttons.labTEXTrad \
     .fRbuttons.txtANGdeg \
     .fRbuttons.labTEXTdeg \
   -side right \
   -anchor e \
   -fill none \
   -expand 0
   

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

##+#####################################################################
##+#####################################################################
## BINDINGS section: none (or 3 bindings on button1-release on the sliders)
##+#####################################################################
##+#####################################################################

bind .fRsliders.fRdeg.scale   <ButtonRelease-1>  {set TYPEchanged "DEG" ; angle_update 0}

bind .fRsliders.fRrad.scale   <ButtonRelease-1>  {set TYPEchanged "RAD" ; angle_update 0}

bind .fRsliders.fRpcnt.scale   <ButtonRelease-1>  {set TYPEchanged "PCNT" ; angle_update 0}


##+#####################################################################
##+#####################################################################
## PROCS section:
##    -  'angle_update'
##    -  'draw_circle'
##    -  'draw_angle'
##    -  'put_vars'
##    -  'popup_msgVarWithScroll'
##+#####################################################################
##+#####################################################################


##+#####################################################################
## 'angle_update' PROCEDURE
##+##################################################################### 
## PURPOSE: As any slider is changed, set 
##             1) the other sliders 
##             2) update the text widgets that hold the angle in 3 units 
##             3) update the angle on the circle on the canvas
##
## CALLED BY:  3 scale widgets:
##               - .fRsliders.fRdeg.scale
##               - .fRsliders.fRrad.scale
##               - .fRsliders.fRpcnt.scale
##+#####################################################################

proc angle_update {x} {

   global TYPEchanged ANGdeg ANGrad ANGpcnt twopi widthChars widthFiller

   # global Rpal255 Gpal255 Bpal255

   if {"$TYPEchanged" == "DEG"} {
      set ANGdeg   [.fRsliders.fRdeg.scale  get]
      set ANGpcnt [expr {$ANGdeg / 3.60}]
      set ANGrad  [expr {$twopi * ($ANGpcnt/100.0)}]
   }

   if {"$TYPEchanged" == "RAD"} {
      set ANGrad   [.fRsliders.fRrad.scale  get]
      set ANGpcnt [expr {100.0 * $ANGrad / $twopi}]
      set ANGdeg  [expr {$ANGpcnt * 3.60}]
   }

   if {"$TYPEchanged" == "PCNT"} {
      set ANGpcnt  [.fRsliders.fRpcnt.scale get]
      set ANGrad  [expr {($ANGpcnt/100.0) * $twopi}]
      set ANGdeg  [expr {$ANGpcnt * 3.60}]
   }

   ## Update the angle on the canvas.

   redraw_angle $ANGdeg

   ## Show the current angle in DEGREES in a small text widget.

   .fRbuttons.txtANGdeg delete 1.0 end
   .fRbuttons.txtANGdeg insert 1.0 "$widthFiller"
   set insertIDX [expr {$widthChars - [string length "$ANGdeg"]}]
   .fRbuttons.txtANGdeg insert 1.$insertIDX $ANGdeg

   ## FOR TESTING:
   #   puts "insertIDX: $insertIDX"

   ## Show the current angle in RADIANS in a small text widget.

   .fRbuttons.txtANGrad delete 1.0 end
   .fRbuttons.txtANGrad insert 1.0 "$widthFiller"
   set insertIDX [expr {$widthChars - [string length "$ANGrad"]}]
   .fRbuttons.txtANGrad insert 1.$insertIDX $ANGrad

   ## FOR TESTING:
   #   puts "insertIDX: $insertIDX"

   ## Show the current angle in PERCENTS in a small text widget.

   .fRbuttons.txtANGpcnt delete 1.0 end
   .fRbuttons.txtANGpcnt insert 1.0 "$widthFiller"
   set insertIDX [expr {$widthChars - [string length "$ANGpcnt"]}]
   .fRbuttons.txtANGpcnt insert 1.$insertIDX $ANGpcnt

   ## FOR TESTING:
   #   puts "insertIDX: $insertIDX"

}
## END of proc 'angle_update'


##+#####################################################################
## 'draw_circle' PROCEDURE
##+#####################################################################
## 
## PURPOSE: To draw a circle in the middle of the canvas. 
## 
## CALLED:  in the 'Additional GUI Initialization' section at the
##          bottom of this script.
##+#####################################################################

proc draw_circle {} {

   # global 

   ## Get the current width & height of the canvas (in pixels). 

   set canWidthPx  [winfo width  .fRdepict.can]
   set canHeightPx [winfo height .fRdepict.can]

   ## FOR TESTING:
   #   puts "canWidthPx : $canWidthPx"
   #   puts "canHeightPx: $canHeightPx"

   set ULXpx [expr {0.1 * $canWidthPx }]
   set ULYpx [expr {0.1 * $canHeightPx}]
   set LRXpx [expr {0.9 * $canWidthPx }]
   set LRYpx [expr {0.9 * $canHeightPx}]

   ## FOR TESTING:
   #  puts "ULXpx: $ULXpx"
   #  puts "ULYpx: $ULYpx"
   #  puts "LRXpx: $LRXpx"
   #  puts "LRYpx: $LRYpx"

   .fRdepict.can create oval \
      $ULXpx $ULYpx $LRXpx $LRYpx \
      -width 2 -outline black

   ## -fill black

}
## END of proc 'draw_circle'


##+#####################################################################
## 'redraw_angle' PROCEDURE
##+#####################################################################
## 
## PURPOSE: To (re)draw an angle on the circle in the middle of the canvas. 
## 
## CALLED BY:  the 'angle_update' proc
##+#####################################################################

proc redraw_angle {degrees} {

   ## Get the current width & height of the canvas (in pixels). 

   set canWidthPx  [winfo width  .fRdepict.can]
   set canHeightPx [winfo height .fRdepict.can]

   ## FOR TESTING:
   #   puts "canWidthPx : $canWidthPx"
   #   puts "canHeightPx: $canHeightPx"

   set ULXpx [expr {0.1 * $canWidthPx }]
   set ULYpx [expr {0.1 * $canHeightPx}]
   set LRXpx [expr {0.9 * $canWidthPx }]
   set LRYpx [expr {0.9 * $canHeightPx}]

   .fRdepict.can delete TAGarc

   ## FOR TESTING:
   #  puts "ULXpx: $ULXpx"
   #  puts "ULYpx: $ULYpx"
   #  puts "LRXpx: $LRXpx"
   #  puts "LRYpx: $LRYpx"

   .fRdepict.can create arc \
      $ULXpx $ULYpx $LRXpx $LRYpx -start 0 -extent $degrees  \
      -width 2 -fill black -outline black -tag TAGarc


}
## END of proc 'redraw_angle'


##+#####################################################################
## PROCEDURE -- put_vars
##
## PURPOSE: Puts a string containing the 3 angles (ANGdeg, ANGrad, ANGpcnt)
##          to standard output. Then exits this GUI.
##
## Called by:  button .fRbuttons.buttOK
##+#####################################################################

proc put_vars { } {

   global ANGdeg ANGrad ANGpcnt

   puts "$ANGdeg $ANGrad $ANGpcnt"

   exit

}
## END of proc  'puts_vars'


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

set HELPtext \
"   **** HELP for this 'tkAngle-Convert-Select' GUI utility ****
   ****                  for angles in                     ****
   ****    radians, degrees, or percent-of-circumference   ****

   This Tk script provides a GUI for showing angles between zero
   and 360 degrees in three different units:
         - degrees
         - radians
         - percents of the circumference of a circle.

   These 3 different units are shown (and selected for conversion
   to the other 2 units) via 3 'sliders' of 3 Tk 'scale' widgets. 

   These 3 different (corresponding) numbers are also shown
   in 3 'text' widgets, a few characters wide --- from which
   a user can copy-and-paste the numbers from this GUI window 
   to some other window. 

   For any 'slider' change, this GUI script IMMEDIATELY
   updates the location of the other 2 'sliders' --- and
   the current angle is depicted on a circle drawn 
   in a 'canvas' wdiget in a corner of the GUI
   window (covering about 20% of the window).

   The 3 text widgets, that show the angle in the 3 different
   units, are also IMMEDIATELY updated whenever any one of
   the 3 scale widget 'sliders' is changed.

   By clicking on a 'UseIt' button on the GUI, this script
   returns a string containing the current angle (in the
   3 different units) to a calling script/application.

 INITIALIZING THE ANGLE:

   This script will accept an angle (in degrees) which is
   used to initialize the
      - setting of the 3 'sliders'
      - the numbers shown in the 3 small text widgets
      - the display of an angle in a circle on the canvas widget.

   The angle can be passed to the script as an argument --- OR,
   if some 'catch' statements in the script are de-commented ---
   the angle can be passed as an environment variable.

 FINE ADJUSTMENTS OF THE SCALES:

   It is typically not obvious to a user who has not used the
   Tk scale widget before that one can easily advance the 'slider'
   one resolution-unit at a time.

   You can mouse-click ON EITHER SIDE OF the slider 'button' to
   advance the slider one resolution-unit per click. Simply
   click repeatedly to adjust the slider to a specific value.

   AND, by clicking on either side and HOLDING DOWN, the slider
   will rapidly advance one resolution-unit at a time UNTIL
   the mouse-button is RELEASED.

 SOME USES of this utility:

   1) Useful for helping math-science students get a visual
      'feel' for the relation between radians and degrees and
      the distance around the circumference of a circle.

   2) Also could be useful for programmers (example: Tcl-Tk programmers)
      for determining angles (example: radians) to be used in
      various Tcl-Tk apps --- example: Tk apps doing 'create arc'
      commands on a Tk 'canvas'.

   3) IN A SHELL SCRIPT OR ANOTHER TK SCRIPT, this Tk script can
      ACT AS AN ANGLE SELECTOR by passing the current angle (in
      the 3 different units) to 'stdout', when the 'UseIt' button
      is clicked.
         Example output string:  180.0  3.14159  0.5

      (When the 'Cancel' button is clicked, the GUI is closed
       WITHOUT sending a text string to 'stdout'.)

Note:
The user can set up this script as an icon on a desktop so that
the GUI can be started up by a click (or two) on the icon.
"

##+###############################################
## ADDITONAL GUI INITIALIZATION (if any) FOLLOWS.
##+###############################################

update
draw_circle
set TYPEchanged "DEG"
angle_update 0


SOME POTENTIAL ENHANCEMENTS:

I do not have any plans for further enhancements for this GUI, but a 'ColorMe' button could be added to this GUI --- like I did for a font-selector GUI whose code is at YAFSG - Yet Another Font Selector GUI and a tkPointerSettings GUI whose code I contributed at tkPointerSettings - a Tk GUI 'wrapper' for the 'xinput' command on this wiki.

With a 'ColorMe' button, the user could easily use a color selector, like the one at A non-obfuscated color selector GUI, to change the color of this tkAngle-Convert-Select GUI.

Instead of supplying a 'ColorMe' button to do that, I simply point out that the user can change the RGB values for the 'tk_setPalette' statement near the top of this code.

IN CONCLUSION

Once in a while, I see questions in forums like 'how can i make several scale widgets move in unison?'. Well, here is some code that answers that particular question --- in detail, with a full example --- not a 'snippet'.

I sometimes wonder how many lines of code it would have taken in C or C++ with a widgets-library like Qt or Gtk or wxWidgets to do something like getting scale widgets to move in unison.

But I don't wonder for long. Back to Tcl-Tk.