Updated 2014-06-07 21:22:11 by uniquename

uniquename - 2012nov29

Here is a diagramming 'end-user utility' that was inspired by a Tk script at the web page A little drawing tool, written by Richard Suchenwirth, 2004 Mar.

On that page, Suchenwirth wrote:
   "As I needed to produce a dataflow drawing, and did not want
   to bother with commercial drawing tools, I just hacked up the
   following thingy.  ...

   You can draw rectangles, ovals, and lines and place text at any
   canvas position ... depending on the mode selected with the
   radiobuttons on top.

   In 'move' mode, you can obviously move items around, until
   they look right. Right-click on an item (in any mode) to
   delete it. ...

   Many more bells and whistles (selection of font family/style/size,
   line width, colors etc.) are conceivable, but the following code
   just did what I wanted, so here it is"

NOTE: That last sentence is what I set out to do --- add more BELLS AND WHISTLES --- allow selection of colors, line width, font family/style/size, etc. --- 8 years after RS's contributed script.

After a few days of work and borrowing heavily from a couple of similar canvas-object-placement scripts that I have written in the past couple of months, namely

and

I arrived at the following Tk GUI.

In this image, you can see the three buttons for color-setting, across the top of the GUI --- 'Fill', 'Outline/Line/Text', and 'Background'.

Under that row of buttons, you can see the 'Add:' radiobuttons, which indicate the 7 types of objects you can place on the canvas with this 'wheeeDiagram' utility:

  • rectangle
  • oval
  • diamond
  • line
  • curve
  • text
  • image

Depending on the radiobutton selected, the frame/line just above the canvas changes, to present some options that are suited to the object chosen.

For example, when the 'RECTANGLE' object is chosen, the 'obj-opts' frame is 'refreshed' with an appropriate set of option widgets, such as:
   - an 'aspect' checkbutton to specify rectangular/square

   - radiobuttons for fill, outline, or both

Similar options are presented for the 'OVAL' and 'DIAMOND' objects.

When the 'LINE' or 'CURVE' object is chosen, the 'obj-opts' frame is 'refreshed' with option widgets, such as:
   - a row of 'arrow-head' radiobuttons to specify end1/end2/both/none

And when the 'TEXT' or 'IMAGE' object is chosen, an entry field is presented in the 'obj-opts' frame. The 'IMAGE' options include a 'Browse...' button to bring up the Tk file-selector utility to aid in providing a complete filename.

Most operations on the canvas are accomplished with MB 1, 2, or 3 --- where MB = mouse-button.

Once you put an object on the canvas, it is easy to move an object (with MB2) --- and easy to delete an object (with MB3).

The 'Help' button shows the following text. It describes how easy it is to add/move/delete objects.

HELP for the WheeeDiagram Utility

Basically, this utility

  • ADDS objects with MB1 (mouse button 1),
  • MOVES objects with MB2
  • DELETES objects with MB3.

That may be all you need to know.

You can tell from the radiobuttons on the GUI that:

You can draw rectangles, ovals, diamonds, lines, and curves on the canvas (via mouse actions) --- and you can place text and images on the canvas (via entry fields). Use the 'Add:' radiobuttons to determine which objects to add.

ADDING objects is done with MB1 (Mouse Button 1), for most objects. Choose an object type with the 'Add:' radiobuttons. Then press MB1 to establish a starting point.

Drag MB1 to 'fill out' the object --- rectangle, oval, diamond, line, or curve. Release MB1 to finish off an object and see the object count incremented.

After an object is added, you can MOVE it with MB2: Press MB2 down when the mouse cursor is over an object. Keep pressing MB2 and move the mouse to drag the object where it is needed. Release MB2 to end the move.

Once an object is added, you can DELETE it with MB3: Press MB3 down when the mouse cursor is over the object. Release MB3 to delete the object. Object count is decremented.

Currently text-lines and images are placed at the top-left corner of the canvas --- after pressing the Enter key when 'in' the text or image-filename entry field. Alternatively, MB3-click on the entry field to place the object on the canvas.

You can drag text and images to where you want them. (A future release may use MB1 to place text and images --- like the other objects.)

Currently text is added a line at a time. Drag the lines into place --- one line under another. (In a future release, multi-line text entry may be 'enhanced'. The initial purpose of this utility was to make geometric drawings with short text entries of one or two characters, for the most part.)

The following image shows all the object types on one canvas.

In this particular case, I put the image on the canvas last. The image was covering some of the objects on the canvas. I used a 'LowerImages' button (beside the 'Browse...' button of the image-opts) to allow the text and curve on the canvas to appear on top of the image.

---

Some uses of wheeeDiagram

This Tk GUI script was intended to facilitate the creation of 'diagrams' --- such as

  • geometrical diagrams (of 2D and 3D configurations)
  • flowcharts (for example, of program code)
  • hierarchy charts (for example, for a company/organization)
  • decision-trees
  • diagrams for T-shirts
  • and no doubt you can think of more applications.

The code

I provide the code for this 'wheeeDiagram' Tk-GUI script below.

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.

  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 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 Tk 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 labels and buttons and scales stay fixed in size and relative-location as the window is re-sized --- while the 'canvas' expands/contracts as 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.

___

In addition, 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 color buttons call on a color-selector-GUI script to set the 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.

Furthermore, there is a 'Font' button beside the entry field that appears when the 'Text' object is chosen. That 'Font' button calls on a font-selector-GUI script to set a font (family-size-weight-slant). You can make that font-selector script by cutting-and-pasting the code from the page YAFSG - Yet Another Font 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.

___

One interesting feature of this GUI is the way the GUI changes when an object-radiobutton is clicked --- the widgets in the 'obj-opts' frame change.

This 'dynamic' change of the GUI is accomplished by use of the 'pack forget' command. The code for making the change in the GUI is seen in the proc 'setup_opts-binds_for_objadd'.

___

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 the most amazing things that come out of people's noses.

 Code for Tk script 'wheeeDiagram.tk' :

#!/usr/bin/wish -f
##
## SCRIPT: wheeeDiagram.tk
##
## PURPOSE:  This Tk GUI script facilitates the creation of 'diagrams' ---
##           such as 
##               - geometrical diagrams (of 2D and 3D configurations), 
##               - flowcharts (for example, of program code),
##               - hierarchy charts (for example, for a company/organization),
##               - decision-trees, etc.
##
##           This script forms the diagrams by using a Tk canvas and
##           providing ways for the user to put objects on the canvas.
##
##           The objects may be
##              - straight line segments (colored)
##              - 'freehand' lines/curves (colored)
##              - rectangles/squares --- color-filled, color-outline, or  both
##              - ovals/circles --- color-filled, color-outline, or  both
##              - diamond shapes --- color-filled, color-outline, or  both
##              - text - choice of color and font (family, weight, size, slant)
##              - images (from GIF or PNG files)
##
##           Other polygons (or some kinds of polygons) besides rectangles & diamonds
##           --- equilateral or free-form --- MAY BE ADDED IN THE FUTURE.
##           For example:
##              - polygons (any number of sides, edges of any length,
##                    any location) -- color-filled, color-outline, or both.
##           For now, the user can probably make-do with straight-line segments.
##
##           The objects that can be added are indicated by radiobuttons on the GUI.
##           When the user selects an 'object' to draw, a set of 'objopts' widgets
##           is shown in an 'objopts' frame, to govern the drawing of the object.
##
##           This script puts objects on the canvas with commands like
##           'create line', 'create rectangle', 'create oval', 'create polygon'
##           (for diamond), 'create text', and 'create image' --- according to
##           user-chosen parameters that are set via the radiobutton, 
##           checkbutton, entry, scale, button widgets of the 'objopts' frames.
##
##           The rectangle, oval, diamond, line, curve, and text objects can be
##           created with user-specified COLORS --- both outline color and fill
##           color in the case of rectangle, oval, and diamond.
##
##           The user can DRAG objects (with MB2=mouse-button2) to move them.
##
##           And (very important for quickly correcting mistakes and for re-doing
##           the various objects) the user can easily DELETE individual objects
##           --- by simply 'right-clicking' (MB3-click-release) on them.
##      
##           Furthermore, the canvas widget can be given a 'BACKGROUND'
##           COLOR of the user's choice.
##
## CREDITS:  This Tk script was inspired by a Tk script at the web page
##           'A little drawing tool' - http://wiki.tcl.tk/11198 - 
##           by Richard Suchenwirth, 2004 Mar.
##
## On that page, Suchenwirth wrote:
##
## "As I needed to produce a dataflow drawing, and did not want
## to bother with commercial drawing tools, I just hacked up the 
## following thingy.
##
## Most of the code, regarding editable text items on the canvas,
## is borrowed from Brent Welch's book, and only slightly modified.
##
## You can draw rectangles, ovals, and lines and place text at any
## canvas position (multiline is possible, just type <Return>
## for a new line), depending on the mode selected with the
## radiobuttons on top.
##
## In 'move' mode, you can obviously move items around, until
## they look right. Right-click on an item (in any mode) to
## delete it. ...
##
## Many more bells and whistles (selection of font family/style/size,
## line width, colors etc.) are conceivable, but the following code
## just did what I wanted, so here it is"
##
## NOTE: That last sentence is what I plan to do in this script --- add
##       more BELLS AND WHISTLES --- allow selection of colors, line width,
##       font family/style/size, etc. --- 8 years after RS's contributed script.
##
##+##########
## GUI DESIGN:
##
##   Canvas: The GUI made by this Tk script contains a rectangular
##           CANVAS WIDGET on which the  objects will be drawn ---
##           with user-specified 'fill' and 'outline' colors.
##
##  The following frames are available above the canvas frame.
##
##  Buttons:  In a ROW (FRAME) OF BUTTONS, there are 'Exit', 'Help', and
##            'Clear' buttons as well as color choice buttons.
##            The color buttons include:
##
##            - a button that calls a color selector GUI to set the 'fill'
##              color of objects. 
##
##            - a button that calls the same color selector GUI to set the
##              'outline' color of objects that support that option
##               (rectangle, oval, diamond).
##
##            - a button that calls the same color selector GUI to set a
##               background color --- the color of the canvas.
##
##   Objects:  In a ROW (FRAME) OF RADIOBUTTONS, there are object-options
##             for objects such as 'rectangle' 'oval' 'diamond' 'line'
##             'curve' 'text' 'image'.  Also a 'MoveAny' radiobutton is shown.
##
##   Line-width & counts: A line-width scale is available to determine the
##              width of lines for many objects: 'line' 'curve' 'rectangle'
##              'oval' 'diamond'. Counts may be shown in the same frame.
##
##   Object-opts: In a frame BELOW the frame of OBJECT-RADIOBUTTONS is a
##                frame for 'OBJECT-OPTIONS'.
##
##                For example, when 'LINE' object is chosen, the obj-opts
##                frame will clear and be 'refreshed' with various
##                option widgets, such as:
##
##                   - a row of 'arrow-head' radiobuttons to specify
##                     end1/end2/both/none
##                   - a row of 'line-type' radiobuttons to specify
##                     solid/dashed/dotted/whatever line type
##
##                For example, when 'RECTANGLE' object is chosen, the obj-opts
##                frame will clear and be 'refreshed' with various
##                option widgets, such as:
##
##                   - radiobuttons for fill, outline, or both
##                   - an 'aspect' checkbutton to specify rectangular/square
##
##                For example, when 'OVAL' object is chosen, the obj-opts
##                frame will clear and be 'refreshed' with various
##                option widgets, such as:
##
##                   - radiobuttons for fill, outline, or both
##                   - an 'aspect' checkbutton to specify ellipse/circle
##
##                For example, when 'DIAMOND' object is chosen, the obj-opts
##                frame will clear and be 'refreshed' with various
##                option widgets, such as:
##
##                   - radiobuttons for fill, outline, or both
##                   - an 'equilateral' checkbutton (defaulted to off)
##
##
##           NOTE:  There may be reason to have more 'global options', in
##                  addition to the 'line-width' scale, which is used 
##                  for objects with lines and outlines.
##
##                  Perhaps we may eventually add 'global' options:
##                      - 'line-density' radiobuttons to specify
##                         solid/dashed/dotted/whatever outlines/lines.
##                  and
##                      - a global 'equilateral' checkbutton --- to be used
##                        in 'rectangle', 'oval', 'diamond' creation.
##
##   Modes:  I have tried to steer away from setting 'modes of operation'
##           --- like 'create' (add), 'move', 'change', and 'delete' ---
##           by adding more GUI elements, such as multiple RADIOBUTTONS
##           FOR 'MODES OF OPERATION'.
##
##           For now, we use MB1 for add/create, MB2 for moves, and
##           MB3 for deletes.
##
##           'Change' can ordinarily be handled by a 'delete' followed
##           by an 'add'. For example, to change the fill-color of
##           a rectangle, the rectangle could be deleted and re-created.
##
##           That is not ideal --- and if one has text on the rectangle
##           it may be significantly counter-productive. So, someday
##           or over time, some more efficient 'change' capabilities
##           may be added. For example, a 'zoom' (or object 'scaling')
##           capability may be added. Also, a 'RaiseText' button may
##           be added to the GUI.
##
## USING THE GENERATED IMAGE:
##           A screen/window capture utility (like 'gnome-screenshot'
##           on Linux) can be used to capture the GUI image in a GIF
##           or PNG 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 smaller image
##           fsuitable for a web page or an email.
##
##           The editor could also be used to blur the image slightly to
##           'feather' the edges of the rectangles, ovals, lines, and text.
##
##           The colored image file could be used with a utility (like the
##           ImageMagick 'convert' command) to change the outer, background
##           color to TRANSPARENT, making a partially transparent GIF
##           (or PNG) file.
##
##           The image could also be taken into a scalable vector graphics
##           (SVG) editor (like Inkscape on Linux) and the SVG editor used
##           to add anti-aliased text to the image. OR, the shapes can be
##           used as an underlying pattern to reproduce the shapes as a
##           scalable shape, by using curve drawing tools of the editor.
##
##+########################################################################
## 'CANONICAL' STRUCTURE OF THIS TK CODE:
##
##  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 (that are to show inititally).
##
##  2) Define all widgets in the frames, frame-by-frame.
##     When ALL the widgets for a frame are defined, pack ALL the widgets.
##
##  3) Define keyboard and mouse/touchpad/touch-senisitive-screen 'event'
##     BINDINGS, if needed.
##
##  4) Define PROCS, if needed.
##
##  5) Additional GUI INITIALIZATION (typically with one or two of the procs),
##     if needed/wanted.
##
##
## Some detail about the code structure of this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :  '.fRbuttons'  '.fRobjects'  '.fRopts'   '.fRobjopts'
##                   '.fRcanvas'
##
##      Sub-frames: '.fRXXXXoptsN' frames to be contained in '.fRobjopts'
##                   where XXXX = rect or line or ... and N = 1 or 2 or ...
##
##  1b) Pack ALL the frames --- top to bottom.
##
##  2) Define all widgets in the frames (and pack them):
##
##     - In '.fRbuttons':   1 BUTTON widget ('Exit'),
##                          1 BUTTON widget ('Help'),
##                          1 BUTTON widget ('Clear'),
##                            and
##                          3 BUTTONs (for setting the objects'
##                                     fill & outline colors and the
##                                     background/canvas color),
##                            and
##                          1 LABEL widget to display the current color
##                                  values (in hex).
##
##     - In '.fRobjects':   several RADIOBUTTONs for objects such as:
##                          'rect' 'oval' 'diamond' 'line' 'curve' 'text'
##                          'image' --- and 'MoveAny'
##
##     - In '.fRopts':      a SCALE widget to set a 'global' line-width
##                          and
##                          1 label widget, to  show 'status' of the
##                          diagram, such as number of rect/oval/line/...
##                          objects currently on the canvas.
##
##     - In '.fRobjopts':   a frame TO HOLD ONE OR MORE of the following
##                          '.fRXXXXoptsN' FRAMES.
##
##     - In '.fRXXXXopts1': where XXXX depends on the object selected. At
##                          a button1-release on an object-radiobutton in
##                          the frame '.fRobjects',
##                          a proc is executed to 'pack forget' the 'slaves'
##                          in the '.fRobjopts' frame and then recreate the
##                          '.fRobjopts' frame containing the appropriate
##                          '.fRXXXXoptsN' frames. For example, when the 'line'
##                          object is chosen, a '.fRlineopts1' frame is packed
##                          into the .fRobjopts' frame.
##
##     - In '.fRXXXXopts2':  some objects may need an additonal frame with
##                           widgets to specify additional options
##
##     - In '.fRcanvas':     1 'canvas' widget 
##
##  3) Define BINDINGS:
##
##       - a button1-release on the 'object' radiobuttons
##         --- to change the frame(s) within the '.fRobjopts' frame
##         and set up Button1 bindings suited to adding that object.
##
##       - event bindings on the 'objopts' widgets in some cases,
##         such as on the filename-entry widget of the 'image' object,
##         to (re)load the image to the canvas.
##
##       - Button2 bindings --- for moving objects
##
##       - Button3-release binding --- for deleting objects
##
##         (For the current bindings,
##          see the top of the BINDINGS section of this code.)
##
##  4) Define PROCS:
##
##     There are various 'add' 'move' and 'delete' procs for objects
##     on the canvas. (For a list of such procs, see the top of the
##     PROCS section of this code.)
##
##     Some of the buttons in the '.fRbuttons' frame use procs such as:
##
##     - 'set_object_color1'    - shows a color selector GUI and uses the
##                                user-selected color to set a 'fill/line/text' 
##                                color for objects to-be-created on the canvas.
##
##     - 'set_object_color2'    - shows a color selector GUI and uses the
##                                user-selected color to set an 'outline' color 
##                                for some objects to-be-created on the canvas
##                                such as 'rect' 'oval' 'diamond'.
##
##     - 'set_color_background' - shows a color selector GUI and uses the
##                                user-selected color to reset the color of
##                                the canvas background.
##
##     - 'popup_msg_var_scroll' - used to show messages to the user, such as
##                                the HELPtext for this utility
##
##  5) Additional-GUI-initialization:  Set an intial background color but then
##                                     leave a blank canvas.
##
##                                     Set an intial 'objects' radiobutton ON
##                                     and fill frame '.fRobjopts' with the
##                                     appropriate '.fRXXXXoptsN' frame(s). 
##
##+########################################################################
## DEVELOPED WITH:
##   Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala').
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##                                  showed   8.5 8.5   on Ubuntu 9.10
##    after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts.
##+#######################################################################
## MAINTENANCE HISTORY:
## Created by: Blaise Montandon 2012sep18 Started laying out the GUI.
## Changed by: ...... ......... 2012nov25 Re-started laying out the GUI.
##+#######################################################################

##+#######################################################################
## Set WINDOW TITLES.
##+#######################################################################

wm title    . "wheeeDiagram - place Lines,Rectangles,Ovals,Diamonds,Text,Images"
wm iconname . "wheeeDiagram"


##+#######################################################################
## Set WINDOW POSITION.
##+#######################################################################

wm geometry . +15+30


##+######################################################
## Set the COLOR SCHEME for the window ---
## and background colors for its widgets.
## 
## Also set the initial colors for the polygon/oval 'outline'
## and 'fill' colors --- and intial color for the
## canvas background (outside the polygons/ovals).
##+######################################################

# set Rpal 255
# set Gpal 255
# set Bpal 255
set Rpal 210
set Gpal 210
set Bpal 210

set hexPALcolor [format "#%02X%02X%02X" $Rpal $Gpal $Bpal]

tk_setPalette "$hexPALcolor"

## Set color background for some widgets.

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


##+##########################################################
## Set (temporary) FONT-NAMES.
##
## We use a VARIABLE-WIDTH FONT for label and button widgets.
##
## We use a FIXED-WIDTH FONT for text widgets (to preserve
## alignment of columns in text), listboxes (to preserve
## alignment of characters in lists), and entry fields
## (to make it easy to position the text cursor at narrow
## characters like i, j, l, and the number 1).
##+##########################################################

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

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

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


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

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

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



##+###########################################################
## Set GEOMETRY PARAMETERS for the various widget definitions.
## (e.g. width and height of canvas, and padding for Buttons)
##+###########################################################

## CANVAS geom parms:

set initCanWidthPx 400
set initCanHeightPx 300
set minCanHeightPx 24
# set BDwidthPx_canvas 2
set BDwidthPx_canvas 0


## BUTTON geom parameters:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## LABEL geom parameters:

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 geom parameters:

set BDwidthPx_entry 2


##+###################################################################
## Set a MINSIZE of the window (roughly).
##
## 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 \
   " Exit Help  Clear  Fill  Outline/Line/Text  Background "]

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

set minWinWidthPx [expr {32 + $minWinWidthPx}]


## MIN HEIGHT ---
##             2 chars  high for the '.fRbuttons' frame
##             1 char   high for the '.fRobjects' frame
##             2 chars  high for the '.fRopts'    frame
##             1 char   high for the '.fRobjopts' frame
##            24 pixels high for the '.fRcanvas' frame.

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {24 + 6 * $CharHeightPx}]

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

set minWinHeightPx [expr {48 + $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 '.fRbuttons' frame:

set aRtext(buttonEXIT)  "Exit"
set aRtext(buttonHELP)  "Help"
set aRtext(buttonCLEAR) "Clear
Canvas"

set aRtext(buttonFILL)  "Fill
Color"

set aRtext(buttonOUTLINE) "Outline/Line/Text
Color"

set aRtext(buttonBkgdCOLOR) "Background
Color"

## For  '.fRobjects' frame:

set aRtext(labelOBJECTS)   "Add:"
set aRtext(radbuttRECT)    "Rectangle"
set aRtext(radbuttOVAL)    "Oval"
set aRtext(radbuttDIAMOND) "Diamond"
set aRtext(radbuttLINE)    "Line"
set aRtext(radbuttCURVE)   "Curve"
set aRtext(radbuttTEXT)    "Text"
set aRtext(radbuttIMG)     "Image"

# set aRtext(radbuttPOLY)  "Polygon"

## For '.fRopts' frame:

set aRtext(labelLINEWIDTH) "LineWidth
(pixels):"

## For '.fRobjopts' frames (.fRXXXXoptsN):

set aRtext(labelRECT)    "Rectangle opts:"
set aRtext(labelOVAL)    "Oval opts:"
set aRtext(labelDIAMOND) "Diamond opts:"
set aRtext(labelLINE)    "Line opts:"
set aRtext(labelCURVE)   "Curve opts:"
# set aRtext(labelPOLY)    "Polygon opts:"

set aRtext(chkbuttEQUI)     "equilateral"
set aRtext(chkbuttCIRCLE)   "circle"
set aRtext(chkbuttSQUARE)   "square"

set aRtext(radbuttFILL)     "fill"
set aRtext(radbuttOUTLINE)  "outline"
set aRtext(radbuttBOTH)     "both"

set aRtext(labelARROWHEADS)  "Arrowheads at"
set aRtext(radbuttARO1st)    "1st-end"
set aRtext(radbuttARO2nd)    "2nd-end"
set aRtext(radbuttAROboth)   "both-ends"
set aRtext(radbuttAROnone)   "neither"

set aRtext(labelTEXT)        "Text-line:"
set aRtext(buttonFONT)       "Font"
set aRtext(buttonRAISETEXT)  "RaiseText"

set aRtext(labelFILE)    "ImgFilename (GIF/PNG):"
set aRtext(buttonBROWSE)  "Browse ..."
set aRtext(buttonLOWERIMGS) "LowerImages"

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


##+###################################################################
## DEFINE *ALL* THE FRAMES:
## 
##   Top-level :  '.fRbuttons'  '.fRobjects'  '.fRopts'
##                '.fRobjopts'  '.fRcanvas'
##
##   Sub-frames of '.fRobjopts' : 
##               '.fRXXXXoptsN' such as
##                   '.fRlineopts1', '.fRrectopts1',  '.fRrectopts2'
##                (The widgets for these frames will be defined,
##                 but these frames are not packed until they are
##                 called for, by a click on an 'object' radiobutton.)
##+###################################################################

# set BDwidth_frame 2
# set RELIEF_frame raised

set BDwidth_frame 0
set RELIEF_frame flat

frame .fRbuttons  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRobjects  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRopts     -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRcanvas   -relief $RELIEF_frame  -borderwidth $BDwidth_frame


## Define the sub-frames of '.fRobjopts'.
##   (These will be packed within .fRobjopts when called upon,
##    by a click on an 'object' radiobutton.

frame .fRobjopts.fRrectopts1   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts.fRovalopts1   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts.fRdiamondopts1 -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts.fRlineopts1   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts.fRcurveopts1  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts.fRtextopts1   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRobjopts.fRimgopts1    -relief $RELIEF_frame  -borderwidth $BDwidth_frame

# frame .fRobjopts.fRpolyopts1   -relief $RELIEF_frame  -borderwidth $BDwidth_frame

##+##############################
## PACK the top-level FRAMES. 
##+##############################

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

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


##+#########################################################
## OK. Now we are ready to define the widgets in the frames.
##+#########################################################


##+#####################################################################
## In the '.fRbuttons' FRAME  -
## DEFINE
##    - Exit, Help, Clear buttons,
## and
##    - 3 buttons to specify colors (fill, outline, background)
## and
##   - a label widget, to show current color values (in hex)
##+#####################################################################

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_msg_var_scroll "$HELPtext"}

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

button .fRbuttons.buttCOLOR1 \
   -text "$aRtext(buttonFILL)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_object_color1"

button .fRbuttons.buttCOLOR2 \
   -text "$aRtext(buttonOUTLINE)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_object_color2"

button .fRbuttons.buttCOLORbkgd \
   -text "$aRtext(buttonBkgdCOLOR)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_background_color"

label .fRbuttons.labelCOLORS \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label


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

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttCLEAR \
     .fRbuttons.buttCOLOR1 \
     .fRbuttons.buttCOLOR2 \
     .fRbuttons.buttCOLORbkgd \
     .fRbuttons.labelCOLORS \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRobjects' FRAME -
## DEFINE (and PACK) RADIOBUTTON widgets --- for the objects:
## line,curve,rect,oval,diamond,poly,text,img.
##
## Whenever an 'objects' radiobutton is clicked, the '.fRobjopts' frame
## will be 'removed', with 'pack forget', and then 're-established', by 
## re-packing the '.fRobjopts' frame to contain the appropriate
## '.fRXXXXoptsN' frame(s).
##+###################################################################

label .fRobjects.labelOBJECTS \
   -text "$aRtext(labelOBJECTS)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

## The 'curObject' variable for these 'object' radiobuttons
## is set in the added-GUI-initialization section at the
## bottom of this script.

# set curObject "rect"

radiobutton .fRobjects.radbuttRECT \
   -text "$aRtext(radbuttRECT)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "rect" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjects.radbuttOVAL \
   -text "$aRtext(radbuttOVAL)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "oval" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjects.radbuttDIAMOND \
   -text "$aRtext(radbuttDIAMOND)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "diamond" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

if {0} {
radiobutton .fRobjects.radbuttPOLY \
   -text "$aRtext(radbuttPOLY)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "poly" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt
}

radiobutton .fRobjects.radbuttLINE \
   -text "$aRtext(radbuttLINE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "line" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjects.radbuttCURVE \
   -text "$aRtext(radbuttCURVE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "curve" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjects.radbuttTEXT \
   -text "$aRtext(radbuttTEXT)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "text" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjects.radbuttIMG \
   -text "$aRtext(radbuttIMG)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "img" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

## We do not use a MoveAny radiobutton for now.
## We will try to use a Button2-instead-of-Button1 strategy.
if {0} {
radiobutton .fRobjects.radbuttMOVE \
   -text "$aRtext(radbuttMOVE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable curObject \
   -value "move" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt
}

## Pack ALL widgets in the '.fRobjects' frame.

pack .fRobjects.labelOBJECTS \
     .fRobjects.radbuttRECT \
     .fRobjects.radbuttOVAL \
     .fRobjects.radbuttDIAMOND \
     .fRobjects.radbuttLINE \
     .fRobjects.radbuttCURVE \
     .fRobjects.radbuttTEXT \
     .fRobjects.radbuttIMG \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0

#     .fRobjects.radbuttMOVE \

#     .fRobjects.radbuttPOLY \


##+##################################################################
## In the '.fRopts' FRAME -
## DEFINE a SCALE widget (for line width) and
## DEFINE 1 LABEL widget --- to show some
## 'status' info such as # of objects added/deleted.
##+###################################################################

label .fRopts.labelLINEWIDTH \
   -text "$aRtext(labelLINEWIDTH)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

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

scale .fRopts.scaleLINEWIDTH \
   -from 1 -to 12 \
   -resolution 1 \
   -length 120 \
   -font fontTEMP_SMALL_varwidth \
   -variable lineWidthPx \
   -showvalue true \
   -orient horizontal \
   -bd $BDwidthPx_scale \
   -width 10


## Define 'info' label.

label .fRopts.labelINFO \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label


## Pack ALL the widgets in the 'fRopts' frame.

pack  .fRopts.labelLINEWIDTH \
      .fRopts.scaleLINEWIDTH \
      .fRopts.labelINFO \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


## Now define the widgets in the frames that will go within
## the '.fRobjopts' frame.

##+##################################################################
## In the '.fRobjopts.fRrectopts1' FRAME -
## DEFINE (and PACK) a CHECKBUTTON widget (equilateral) and
## several RADIOBUTTON widgets (fill,outline,both).

label .fRobjopts.fRrectopts1.labelRECT \
   -text "$aRtext(labelRECT)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

set rect_equi0or1 0

checkbutton .fRobjopts.fRrectopts1.chkbuttEQUI \
   -text "$aRtext(chkbuttEQUI)" \
   -font  fontTEMP_varwidth \
   -variable rect_equi0or1 \
   -selectcolor "$chkbuttBKGD" \
   -padx $PADXpx_chkbutt \
   -pady $PADYpx_chkbutt \
   -relief raised

set rect_filloutboth "outline"

radiobutton .fRobjopts.fRrectopts1.radbuttFILL \
   -text "$aRtext(radbuttFILL)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable rect_filloutboth \
   -value "fill" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRrectopts1.radbuttOUTLINE \
   -text "$aRtext(radbuttOUTLINE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable rect_filloutboth \
   -value "outline" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRrectopts1.radbuttBOTH \
   -text "$aRtext(radbuttBOTH)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable rect_filloutboth \
   -value "both" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

## Pack ALL the widgets in the '.fRrectopts1' frame.

pack .fRobjopts.fRrectopts1.labelRECT \
     .fRobjopts.fRrectopts1.chkbuttEQUI \
     .fRobjopts.fRrectopts1.radbuttFILL \
     .fRobjopts.fRrectopts1.radbuttOUTLINE \
     .fRobjopts.fRrectopts1.radbuttBOTH \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRobjopts.fRovalopts1' FRAME -
## DEFINE (and PACK) a CHECKBUTTON widget (equilateral) and
## several RADIOBUTTON widgets (fill,outline,both).

label .fRobjopts.fRovalopts1.labelOVAL \
   -text "$aRtext(labelOVAL)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

set oval_equi0or1 0

checkbutton .fRobjopts.fRovalopts1.chkbuttEQUI \
   -text "$aRtext(chkbuttEQUI)" \
   -font  fontTEMP_varwidth \
   -variable oval_equi0or1 \
   -selectcolor "$chkbuttBKGD" \
   -padx $PADXpx_chkbutt \
   -pady $PADYpx_chkbutt \
   -relief raised

set oval_filloutboth "outline"

radiobutton .fRobjopts.fRovalopts1.radbuttFILL \
   -text "$aRtext(radbuttFILL)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable oval_filloutboth \
   -value "fill" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRovalopts1.radbuttOUTLINE \
   -text "$aRtext(radbuttOUTLINE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable oval_filloutboth \
   -value "outline" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRovalopts1.radbuttBOTH \
   -text "$aRtext(radbuttBOTH)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable oval_filloutboth \
   -value "both" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## Pack ALL the widgets in the '.fRovalopts1' frame.

pack .fRobjopts.fRovalopts1.labelOVAL \
     .fRobjopts.fRovalopts1.chkbuttEQUI \
     .fRobjopts.fRovalopts1.radbuttFILL \
     .fRobjopts.fRovalopts1.radbuttOUTLINE \
     .fRobjopts.fRovalopts1.radbuttBOTH \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRobjopts.fRdiamondopts1' FRAME -
## DEFINE (and PACK) a CHECKBUTTON widget (equilateral) and
## several RADIOBUTTON widgets (fill,outline, both).

label .fRobjopts.fRdiamondopts1.labelDIAMOND \
   -text "$aRtext(labelDIAMOND)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

set diamond_equi0or1 0

checkbutton .fRobjopts.fRdiamondopts1.chkbuttEQUI \
   -text "$aRtext(chkbuttEQUI)" \
   -font  fontTEMP_varwidth \
   -variable diamond_equi0or1 \
   -selectcolor "$chkbuttBKGD" \
   -padx $PADXpx_chkbutt \
   -pady $PADYpx_chkbutt \
   -relief raised

set diamond_filloutboth "outline"

radiobutton .fRobjopts.fRdiamondopts1.radbuttFILL \
   -text "$aRtext(radbuttFILL)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable diamond_filloutboth \
   -value "fill" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRdiamondopts1.radbuttOUTLINE \
   -text "$aRtext(radbuttOUTLINE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable diamond_filloutboth \
   -value "outline" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRdiamondopts1.radbuttBOTH \
   -text "$aRtext(radbuttBOTH)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable diamond_filloutboth \
   -value "both" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## Pack ALL the widgets in the '.fRdiamondopts1' frame.

pack .fRobjopts.fRdiamondopts1.labelDIAMOND \
     .fRobjopts.fRdiamondopts1.chkbuttEQUI \
     .fRobjopts.fRdiamondopts1.radbuttFILL \
     .fRobjopts.fRdiamondopts1.radbuttOUTLINE \
     .fRobjopts.fRdiamondopts1.radbuttBOTH \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


##+##################################################################
## IF WE EVER IMPLEMENT A 'POLYGON' OBJECT, we may want
## widgets like the following.
##
## In the '.fRobjopts.fRpolyopts1' FRAME -
## DEFINE (and PACK) a CHECKBUTTON widget (equilateral) and
## several RADIOBUTTON widgets (fill,outline,both)



##+##################################################################
## In the '.fRobjopts.fRlineopts1' FRAME -
## DEFINE (and PACK) several RADIOBUTTON widgets -
## for arrowheads: 1st-end, 2nd-end, both-ends.

label .fRobjopts.fRlineopts1.labelLINE \
   -text "$aRtext(labelLINE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

label .fRobjopts.fRlineopts1.labelARROWHEADS \
   -text "$aRtext(labelARROWHEADS)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

## The 'LINEarrow' variable for these 'line' radiobuttons
## is set in the added-GUI-initialization section at the
## bottom of this script.

# set LINEarrow "aro2nd"

radiobutton .fRobjopts.fRlineopts1.radbuttARO1st \
   -text "$aRtext(radbuttARO1st)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable LINEarrow \
   -value "aro1st" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRlineopts1.radbuttARO2nd \
   -text "$aRtext(radbuttARO2nd)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable LINEarrow \
   -value "aro2nd" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRlineopts1.radbuttAROboth \
   -text "$aRtext(radbuttAROboth)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable LINEarrow \
   -value "aroboth" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRlineopts1.radbuttAROnone \
   -text "$aRtext(radbuttAROnone)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable LINEarrow \
   -value "aronone" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## Pack ALL the widgets in the '.fRlineopts1' frame.

pack .fRobjopts.fRlineopts1.labelLINE \
     .fRobjopts.fRlineopts1.labelARROWHEADS \
     .fRobjopts.fRlineopts1.radbuttARO1st \
     .fRobjopts.fRlineopts1.radbuttARO2nd \
     .fRobjopts.fRlineopts1.radbuttAROboth \
     .fRobjopts.fRlineopts1.radbuttAROnone \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRobjopts.fRcurveopts1' FRAME -
## DEFINE (and PACK) several RADIOBUTTON widgets -
## for arrowheads: 1st-end, 2nd-end, both-ends.

label .fRobjopts.fRcurveopts1.labelCURVE \
   -text "$aRtext(labelCURVE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

label .fRobjopts.fRcurveopts1.labelARROWHEADS \
   -text "$aRtext(labelARROWHEADS)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

## The 'CURVEarrow' variable for these 'curve' radiobuttons
## is set in the added-GUI-initialization section at the
## bottom of this script.

# set CURVEarrow "aro2nd"

radiobutton .fRobjopts.fRcurveopts1.radbuttARO1st \
   -text "$aRtext(radbuttARO1st)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable CURVEarrow \
   -value "aro1st" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRcurveopts1.radbuttARO2nd \
   -text "$aRtext(radbuttARO2nd)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable CURVEarrow \
   -value "aro2nd" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRcurveopts1.radbuttAROboth \
   -text "$aRtext(radbuttAROboth)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable CURVEarrow \
   -value "aroboth" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt

radiobutton .fRobjopts.fRcurveopts1.radbuttAROnone \
   -text "$aRtext(radbuttAROnone)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable CURVEarrow \
   -value "aronone" \
   -selectcolor "$radbuttBKGD" \
   -relief flat \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -bd $BDwidthPx_radbutt


## Pack ALL the widgets in the '.fRcurveopts1' frame.

pack .fRobjopts.fRcurveopts1.labelCURVE \
     .fRobjopts.fRcurveopts1.labelARROWHEADS \
     .fRobjopts.fRcurveopts1.radbuttARO1st \
     .fRobjopts.fRcurveopts1.radbuttARO2nd \
     .fRobjopts.fRcurveopts1.radbuttAROboth \
     .fRobjopts.fRcurveopts1.radbuttAROnone \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRobjopts.fRtextopts1' FRAME -
## DEFINE (and PACK) a BUTTON widget -
## to call on a font-selector GUI.

label .fRobjopts.fRtextopts1.labelTEXT \
   -text "$aRtext(labelTEXT)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

## We initialize this widget var (and others)
## in the GUI initialization section at the
## bottom of this script.

# set ENTRYtext ""

entry .fRobjopts.fRtextopts1.entTEXT \
   -textvariable ENTRYtext \
   -bg "$entryBKGD" \
   -font fontTEMP_fixedwidth \
   -width 30 \
   -relief sunken \
   -bd $BDwidthPx_entry

button .fRobjopts.fRtextopts1.buttFONT \
   -text "$aRtext(buttonFONT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_font"

button .fRobjopts.fRtextopts1.buttRAISETEXT \
   -text "$aRtext(buttonRAISETEXT)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {.fRcanvas.can raise TAGtextline}

label .fRobjopts.fRtextopts1.labelFONT \
   -text "" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label


## Pack ALL the widgets in frame 'fRtextopts1'.

pack  .fRobjopts.fRtextopts1.labelTEXT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRobjopts.fRtextopts1.entTEXT \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRobjopts.fRtextopts1.buttFONT \
     .fRobjopts.fRtextopts1.buttRAISETEXT \
     .fRobjopts.fRtextopts1.labelFONT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRobjopts.fRimgopts1' FRAME -
## DEFINE (and PACK) LABEL,ENTRY,BUTTON widgets -
## to prompt for an image (GIF or PNG) filename.

label .fRobjopts.fRimgopts1.labelFILE \
   -text "$aRtext(labelFILE)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -bd $BDwidthPx_label

set ENTRYfilename ""

entry .fRobjopts.fRimgopts1.entFILENAME \
   -textvariable ENTRYfilename \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -width 30 \
   -relief sunken \
   -bd $BDwidthPx_entry

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

button .fRobjopts.fRimgopts1.buttLOWERIMGS \
   -text "$aRtext(buttonLOWERIMGS)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {.fRcanvas.can lower TAGimg}

## Pack ALL the '.fRimgopts1' widgets.

pack .fRobjopts.fRimgopts1.labelFILE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRobjopts.fRimgopts1.entFILENAME \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack .fRobjopts.fRimgopts1.buttBROWSE \
     .fRobjopts.fRimgopts1.buttLOWERIMGS \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+######################################################
## In the '.fRcanvas' frame -
## DEFINE-and-PACK 1 CANVAS widget,
## with x and y SCROLLBARS.
##+######################################################
## 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 .fRcanvas.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0 \
   -yscrollcommand ".fRcanvas.scrolly set" \
   -xscrollcommand ".fRcanvas.scrollx set"

scrollbar .fRcanvas.scrolly \
   -orient vertical \
   -command ".fRcanvas.can yview"

scrollbar .fRcanvas.scrollx \
   -orient horizontal \
   -command ".fRcanvas.can xview"


## Pack ALL the '.fRcanvas' widgets (scrollbars and canvas).
## NOTE:
## GOOD TO PACK THE SCROLLBARS BEFORE THE CANVAS WIDGET.
## THE CANVAS WIDGET MAY TRY TO TAKE ALL THE FRAME SPACE.

pack  .fRcanvas.scrolly \
   -side left \
   -anchor w \
   -fill y \
   -expand 0

## DO NOT USE '-expand 1' FOR Y-SCROLLBAR. IT ALLOWS Y-SCROLLBAR
## TO X-EXPAND. IT PUTS BLANK SPACE BETWEEN Y-SCROLLBAR & CANVAS.
                
pack .fRcanvas.scrollx \
   -side bottom \
   -anchor s \
   -fill x \
   -expand 0

## DO NOT USE '-expand 1' FOR X-SCROLLBAR. IT ALLOWS X-SCROLLBAR
## TO Y-EXPAND. IT KEEPS THE CANVAS FROM Y-EXPANDING.

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

#   -side left \
#   -anchor center \


##+####################################################
## END OF the DEFINITION OF THE GUI FRAMES-and-WIDGETS.
##
## Ready to define BINDINGS and PROCS.
##+####################################################


##+#################################################
## BINDINGS SECTION:
##+#################################################

bind .fRobjects.radbuttRECT   <ButtonRelease-1>  {setup_opts-binds_for_objadd}

bind .fRobjects.radbuttOVAL   <ButtonRelease-1>  {setup_opts-binds_for_objadd}

bind .fRobjects.radbuttDIAMOND <ButtonRelease-1> {setup_opts-binds_for_objadd}


bind .fRobjects.radbuttLINE   <ButtonRelease-1>  {setup_opts-binds_for_objadd}

bind .fRobjects.radbuttCURVE  <ButtonRelease-1>  {setup_opts-binds_for_objadd}

bind .fRobjects.radbuttTEXT   <ButtonRelease-1>  {setup_opts-binds_for_objadd} 

bind .fRobjects.radbuttIMG    <ButtonRelease-1>  {setup_opts-binds_for_objadd}

## We do not implement a 'MoveAny' radiobutton, yet. We wil first try
## to do moves with Button2 rather than Button1.
# bind .fRobjects.radbuttMOVE   <ButtonRelease-1>  {setup_opts-binds_for_objadd}

## We do not implement adding polygons, yet.
# bind .fRobjects.radbuttPOLY   <ButtonRelease-1>  {setup_opts-binds_for_objadd}


## For ADDING objects: Bindings are set via the 'setup_opts-binds_for_objadd' proc
##                     according to value in $curObject.

## For MOVING objects: A button1 binding should be set by a radbutt, above.
##                     Otherwise, the following binding interferes with bindings
##                     for drawing lines, curves, rectangles, ovals, diamonds.
## bind .fRcanvas.can <Button1-Motion>  {move_object}
##
## Alternatively, we may try button2 for object moves.

bind .fRcanvas.can <ButtonPress-2>    {move_grab %x %y}
bind .fRcanvas.can <Button2-Motion>   {move_object %x %y}
bind .fRcanvas.can <ButtonRelease-2>  {move_end %x %y}


## For DELETING objects:
## We give the user the opportunity to bail out of the delete, by moving
## the mouse cursor off of the canvas before releasing button3.
## And we only delete the object nearest the current cursor location.
## (The 'delete_object' proc may need some improvement to make sure
##  that we delete precisely the object desired.)

bind .fRcanvas.can <ButtonRelease-3> {delete_object %x %y}


## For ZOOMING objects:
## The zooming can be done centered at the %x %y coords,
## using the canvas 'scale' command.

bind .fRcanvas.can <MouseWheel>  {zoom_object %D %x %y}


##+######################################################################
## PROCS SECTION:
## (basically in alphabetic order, except for last proc to popup msg)
##
##  The following 'add_...' procs are called by Button1 bindings
##  that are set in calls to 'setup_opts-binds_for_objadd', per $curObject.
##
##  - add_init_curve        - to establish start point of curve by button1-press
##  - add_points_to_curve   - to add points to curve according to button1-?
##  - add_end_curve         - to increment curve count and display the count
##
##  - add_init_diamond      - to establish start point of diamond by button1-press
##  - add_drag_diamond      - to delete-and-redraw diamonds according to button1-motion 
##  - add_diamond           - to increment diamond count and display the count
##
##  - add_image             - to add an image to canvas and update image count in label
##
##  - add_init_line         - to establish start point of line by button1-press
##  - add_drag_line         - to delete-and-redraw lines according to button1-motion 
##  - add_line              - to increment line count and display the count
##
##  - add_init_oval         - to establish start point of oval by button1-press
##  - add_drag_oval         - to delete-and-redraw ovals according to button1-motion
##  - add_oval              - to increment oval count and display the count
##
##  - add_init_polygon      - (not implemented, yet)
##  - add_drag_polygon      - (not implemented, yet)
##  - add_end_polygon       - (not implemented, yet)
##
##  - add_init_rect         - to establish start point of rectangle by button1-press
##  - add_drag_rect         - to delete-and-redraw rectangles according to button1-motion
##  - add_end_rect          - to increment rectangle count and display the count
##
##  - add_text_line         - to add a text-line to canvas and update counts label
##
##  - clear_canvas          - to delete all items from the canvas (called by 'Clear' button)
##
##  - delete_object         - to delete a 'nearby' object (called by button3-release binding)
##
##  - get_chars_before_last - used in 'get_img_filename' to set curDIR
##
##  - get_img_filename      - to get the filename of an image (GIF/PNG) file
##                            and place the image on the canvas (called by 'add_image' proc)
##
##  - move_object           - to move a 'nearby' object (called by button1-motion binding,
##                                           which is set by click on 'MoveAny' radiobutton)
##
##  - set_background_color  - called by background color button '-command'
##
##  - set_font              - called by Font button of text-line opts
##
##  - set_object_color1     - called by color1 (fill/line/text) button '-command'
##  - set_object_color2     - called by color2 (outline) button '-command'
##
##  - setup_opts-binds_for_objadd - according to value of $curObject, puts appropriate
##                            .fRXXXXoptsN frame(s) in .fRobjopts frame --- and sets up
##                            'add_..._XXXX' procs in button1 bindings.
##
##  - update_colors_label   - called by the 3 set-colors procs above
##
##  - update_info_label     - to put current object counts in a label; called by the
##                            add and delete procs after they increment/decrement a count           
##
##  - popup_msg_var_scroll  - called by the Help button
##+#######################################################################


##+#####################################################################
## proc 'add_init_curve'
## PURPOSE: Establishes a start point for a multi-point curve.
##          Puts xy coords in global var curvePoints.
##
##   Also sets arrow-head (and other) draw parms for the
##   canvas 'create line' command in proc 'add_drag_curve',
##   according to current curve-opts settings in the '.fRobjopts' frame.
##
## CALLED BY: button1-press binding on the canvas. The add-curve bindings
##            are setup via a click on the 'curve' objects-radiobutton.
##+#####################################################################

proc add_init_curve {x y} {

   global curvePoints curveDrawOpts CURVEarrow
 
   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]
   
   set curvePoints [list $x $y]

   ## Set curve drawing opts in var curveDrawOpts.
   ## Possible '-arrow' values: none first last both
   ##
   ## Other line parms to consider:
   ## arrowshape, capstyle, joinstyle, smooth, splinesteps

   if {"$CURVEarrow" == "aro1st"} {
      set curveDrawOpts "-arrow first"
   } elseif {"$CURVEarrow" == "aro2nd"} {
      set curveDrawOpts "-arrow last"
   } elseif {"$CURVEarrow" == "aroboth"} {
      set curveDrawOpts "-arrow both"
   } else {
      set curveDrawOpts "-arrow none"
   }

}
## END OF proc 'add_init_curve'


##+#####################################################################
## proc 'add_points_to_curve'
## PURPOSE:  Draws a curve on the canvas. Gets previous points
##           from global var curvePoints.
##           Gets new point from xy passed into this proc, adjusted
##           for canvas extent.
##           Keeps adding points according to button1-motion.
##
## CALLED BY: button1-motion binding on the canvas. The add-curve bindings
##            are setup via a click on the 'curve' objects-radiobutton. 
##+#####################################################################

proc add_points_to_curve {x y} {

   global lineWidthPx COLOR2hex curvePoints curveDrawOpts
 
   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   ## Draw a curve, including setting a tag.

   # catch {.fRcanvas.can delete $curCurveID}
   # curCurveID [ ... ]

   lappend curvePoints $x $y

   eval .fRcanvas.can create line \
      $curvePoints \
      -width $lineWidthPx -fill $COLOR2hex \
      -joinstyle round -capstyle round -smooth 1 \
      $curveDrawOpts -tags TAGcurve

}
## END OF proc 'add_points_to_curve'


##+#####################################################################
## proc 'add_end_curve'
## PURPOSE:  Increments curve count, because button1-release
##           indicates curve draws (via 'add_points_to_curve') are done.
##
## CALLED BY: button1-release binding on the canvas. The add-curve bindings
##            are setup via a click on the 'curve' objects-radiobutton.
##+#####################################################################

proc add_end_curve {x y} {

   global Ncurves

   incr Ncurves

   ## Update counts in the status frame label widget.
   update_info_label

}
## END OF proc 'add_end_curve'


##+#####################################################################
## proc 'add_init_diamond'
## PURPOSE: Establishes a start point for a diamond.
##          Puts xy coords in global vars lineXinit lineYinit.
##
##   Also sets fill/outline/both draw parms for the
##   canvas 'create polygon' command in proc 'add_drag_diamond',
##   according to current diamond-opts settings in the '.fRobjopts' frame.
##
## CALLED BY: button1-press binding on the canvas. The add-diamond bindings
##            are setup via a click on the 'diamond' objects-radiobutton.
##+#####################################################################

proc add_init_diamond {x y} {

   global diamondXinit diamondYinit diamond_filloutboth \
      COLOR1hex COLOR2hex diamondDrawOpts curDiamondID

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]
   
   set diamondXinit $x
   set diamondYinit $y

   ## Set '-fill' and '-outline' parms in var diamondDrawOpts.
   ## NOTE: We need '-fill {}' if we want outline-only.
   ## 'create polygon' works differently from 'create rectangle'
   ## and 'create oval' in this regard.

   set diamondDrawOpts ""
   if { "$diamond_filloutboth" == "fill"} {
      set diamondDrawOpts "-fill $COLOR1hex"
   } elseif { "$diamond_filloutboth" == "outline"} {
      set diamondDrawOpts "-outline $COLOR2hex -fill {}"
   } else {
      set diamondDrawOpts "-fill $COLOR1hex -outline $COLOR2hex"
   }

   ## Unset the var curDiamondID so that we do not delete an existing
   ## diamond with the canvas 'delete $curDiamondID' command in the
   ## proc 'add_drag_diamond'.

   # catch {unset $curDiamondID}
   set curDiamondID ""
}
## END OF PROC 'add_init_diamond'


##+#####################################################################
## proc 'add_drag_diamond'
## PURPOSE:  Draws a 'diamond' shape on the canvas. Gets start point
##           from global vars diamondXinit diamondYinit.
##           Gets a y-value from xy passed into this proc, adjusted
##           for canvas extent. Draws the diamond centered on diamondXinit
##           according to the y-value passed in.
##           Keeps deleting and re-drawing diamonds for button1-motion.
##
## CALLED BY: button1-motion binding on the canvas. The add-diamond bindings
##            are setup via a click on the 'diamond' objects-radiobutton.
##+#####################################################################

proc add_drag_diamond {x y} {

   global diamondXinit diamondYinit diamondDrawOpts diamond_equi0or1 \
      curDiamondID lineWidthPx

   ## diamond_filloutboth, COLOR1hex, and COLOR2hex were used to set
   ## diamondDrawOpts.  They are not needed in this global statement.


   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]


   ## Adjust the current y coordinate to make the diamond equilateral,
   ## if that was requested.

   if {$diamond_equi0or1 == 1} {
      set ynew [expr {$diamondYinit + ($x - $diamondXinit)}]
   } else {
      set ynew $y
   }


   ## Calculate the 4 points at the top bottom and sides of
   ## the diamond.

   set deltX [expr {$x - $diamondXinit}]
   set halfX [expr {$deltX / 2}]
   set deltY [expr {$ynew - $diamondYinit}]
   set halfY [expr {$deltY / 2}]

   set topX [expr {$diamondXinit + $halfX}]
   set topY $diamondYinit

   set leftX $diamondXinit
   set leftY [expr {$diamondYinit + $halfY}]

   set rightX $x
   set rightY [expr {$diamondYinit + $halfY}]

   set lowX [expr {$diamondXinit + $halfX}]
   set lowY $ynew

   ## Draw the diamond, including setting a tag.

   catch {.fRcanvas.can delete $curDiamondID}

   # if {"$curDiamondID" != ""}  {.fRcanvas.can delete $curDiamondID}

   ## FOR TESTING:
   #  puts "proc 'add_drag_diamond' > BEFORE create - curDiamondID: $curDiamondID"

   set curDiamondID [eval .fRcanvas.can create polygon \
      $topX $topY \
      $rightX $rightY \
      $lowX $lowY \
      $leftX $leftY \
      -width $lineWidthPx $diamondDrawOpts -tags TAGdiamond]

   ## FOR TESTING:
   #  puts "proc 'add_drag_diamond' > AFTER create - curDiamondID: $curDiamondID"
   #  puts "proc 'add_drag_diamond' > AFTER create - diamondDrawOpts: $diamondDrawOpts"

}
## END OF proc 'add_drag_diamond'


##+#####################################################################
## proc 'add_end_diamond'
## PURPOSE:  Increments diamond count, because button1-release
##           indicates diamond draws (via 'add_drag_diamond') are done.
##
## CALLED BY: button1-release binding on the canvas. The binding is
##            setup via a click on the 'diamond' objects-radiobutton. 
##+#####################################################################

proc add_end_diamond {x y} {

   global Ndiamonds

   incr Ndiamonds

   ## Update counts in the status frame label widget.

   update_info_label

}
## END OF proc 'add_end_diamond'


##+##################################################################
## proc  'add_image'
##+##################################################################
## PURPOSE: Puts an image on the canvas (in the upper left corner)
##          based on the image filename currently in the
##          image-filename-entry-field.
##
##          (The user can move the image where wanted. We may change
##           this to accept xy location as args to this proc.)
##          
##          Also augments the Nimages count.
##
## CALLED BY:  two bindings on the image-filename-entry-field ---
##             one binding for Return-key and one for button1-release.
##            (We may change this to a button1-release binding on
##             the canvas, with xy loc passed as args.)
##+##################################################################

proc add_image {} {

   global Nimages ENTRYfilename imgID

   set imgID [image create photo -file "$ENTRYfilename"]

   .fRcanvas.can create image 0 0 -anchor nw -image $imgID -tag TAGimg

   incr Nimages

   ## Update counts in the status frame label widget.
   update_info_label
}
## END OF PROC  'add_image'


##+#####################################################################
## proc 'add_init_line'
## PURPOSE: Establishes a start point for a straight line.
##          Puts xy coords in global vars lineXinit lineYinit.
##
##   Also sets arrow-head (and other) draw parms for the
##   canvas 'create line' command in proc 'add_drag_line',
##   according to current line-opts settings in the '.fRobjopts' frame.
##
## CALLED BY: button1-press binding on the canvas. The add-line bindings
##            are setup via a click on the 'line' objects-radiobutton.
##+#####################################################################

proc add_init_line {x y} {

   global lineXinit lineYinit lineDrawOpts LINEarrow curLineID

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]
   
   set lineXinit $x
   set lineYinit $y

   ## Set line drawing opts in var lineDrawOpts.
   ## Possible '-arrow' values: none first last both
   ##
   ## Other line parms to consider:
   ## arrowshape, capstyle, joinstyle, smooth, splinesteps

   if {"$LINEarrow" == "aro1st"} {
      set lineDrawOpts "-arrow first"
   } elseif {"$LINEarrow" == "aro2nd"} {
      set lineDrawOpts "-arrow last"
   } elseif {"$LINEarrow" == "aroboth"} {
      set lineDrawOpts "-arrow both"
   } else {
      set lineDrawOpts "-arrow none"
   }

   ## Unset the var curLineID so that we do not delete an existing
   ## line with the canvas 'delete $curLineID' command in the
   ## proc 'add_drag_line'.

   # catch {unset $curLineID}
   set curLineID ""
}
## END OF proc 'add_init_line'


##+#####################################################################
## proc 'add_drag_line'
## PURPOSE:  Draws a straight line on the canvas. Gets start point
##           from global vars lineXinit lineYinit.
##           Gets end point from xy passed into this proc, adjusted
##           for canvas extent.
##           Keeps deleting and re-drawing lines according to button1-motion.
##
## CALLED BY: button1-motion binding on the canvas. The add-line bindings
##            are setup via a click on the 'line' objects-radiobutton. 
##+#####################################################################

proc add_drag_line {x y} {

   global lineWidthPx COLOR2hex lineXinit lineYinit lineDrawOpts curLineID
 
   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   ## Draw a line, including setting a tag.

   catch {.fRcanvas.can delete $curLineID}

   set curLineID [eval .fRcanvas.can create line \
      $lineXinit $lineYinit $x $y \
      -width $lineWidthPx -fill $COLOR2hex \
      $lineDrawOpts -tags TAGline]

}
## END OF proc 'add_drag_line'


##+#####################################################################
## proc 'add_end_line'
## PURPOSE:  Increments straight line count, because button1-release
##           indicates line draws (via proc 'add_drag_line') are done.
##
## CALLED BY: button1-release binding on the canvas. The add-line bindings
##            are setup via a click on the 'line' objects-radiobutton.
##+#####################################################################

proc add_end_line {x y} {

   global Nlines

   incr Nlines

   ## Update counts in the status frame label widget.

   update_info_label

}
## END OF proc 'add_end_line'


##+#####################################################################
## proc 'add_init_oval'
## PURPOSE: Establishes a start point for an oval.
##          Puts xy coords in global vars ovalXinit ovalYinit.
##
##   Also sets fill/outline/both draw parms for the
##   canvas 'create oval' command in proc 'add_drag_oval',
##   according to current oval-opts settings in the '.fRobjopts' frame.
##
## CALLED BY: button1-press binding on the canvas. The add-oval bindings
##            are setup via a click on the 'oval' objects-radiobutton.
##+#####################################################################

proc add_init_oval {x y} {

   global ovalXinit ovalYinit oval_filloutboth ovalDrawOpts \
      COLOR1hex COLOR2hex curOvalID

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]
   
   set ovalXinit $x
   set ovalYinit $y

   ## Set '-fill' and '-outline' parms in var ovalDrawOpts.

   set ovalDrawOpts ""
   if { "$oval_filloutboth" == "fill"} {
      set ovalDrawOpts "-fill $COLOR1hex"
   } elseif { "$oval_filloutboth" == "outline"} {
      set ovalDrawOpts "-outline $COLOR2hex"
   } else {
      set ovalDrawOpts "-fill $COLOR1hex -outline $COLOR2hex"
   }

   ## Unset the var curOvalID so that we do not delete an existing
   ## oval with the canvas 'delete $curOvalID' command in the
   ## proc 'add_drag_oval'.

   # catch {unset $curOvalID}
   set curOvalID ""
}
## END OF proc 'add_init_oval'


##+#####################################################################
## proc 'add_drag_oval'
## PURPOSE:  Draws an oval on the canvas. Gets start point
##           from global vars ovalXinit ovalYinit.
##           Gets end point from xy passed into this proc, adjusted
##           for canvas extent.
##   Keeps deleting and re-drawing ovals according to button1-motion.
##
## CALLED BY: button1-motion binding on the canvas. The add-oval bindings
##            are setup via a click on the 'oval' objects-radiobutton. 
##+#####################################################################

proc add_drag_oval {x y} {

   global ovalXinit ovalYinit ovalDrawOpts curOvalID oval_equi0or1 lineWidthPx
   ## oval_filloutboth, COLOR1hex, and COLOR2hex were used to set ovalDrawOpts.
   ## They are no longer needed in this global statement.


   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   ## Delete previous oval, if any, and (re)draw the oval,
   ## including setting a tag, to help decrement counts in proc 'delete_object'.

   catch {.fRcanvas.can delete $curOvalID}

   ## Adjust the current y coordinate to make the oval circular,
   ## if that was requested.

   if {$oval_equi0or1 == 1} {
      set ynew [expr {$ovalYinit + ($x - $ovalXinit)}]
   } else {
      set ynew $y
   }

   set curOvalID [eval .fRcanvas.can create oval \
      $ovalXinit $ovalYinit $x $ynew \
      -width $lineWidthPx $ovalDrawOpts -tags TAGoval]

}
## END OF proc 'add_drag_oval'


##+#####################################################################
## proc 'add_end_oval'
## PURPOSE:  Increments oval count, because button1-release
##           indicates oval draws (via proc 'add_drag_oval') are done.
##
## CALLED BY: button1-release binding on the canvas. The add-oval bindings
##            are setup via a click on the 'oval' objects-radiobutton. 
##+#####################################################################

proc add_end_oval {x y} {

   global Novals

   incr Novals

   ## Update counts in the status frame label widget.

   update_info_label

}
## END OF proc 'add_end_oval'


##+#####################################################################
## proc 'add_init_rect'
## PURPOSE: Establishes a start point for an rectangle.
##          Puts xy coords in global vars rectXinit rectYinit.
##
##   Also sets fill/outline/both draw parms for the
##   canvas 'create rectangle' command in proc 'add_drag_rect',
##   according to current rectangle-opts settings in the '.fRobjopts' frame.
##
## CALLED BY: button1-press binding on the canvas. The add-rectangle bindings
##            are setup via a click on the 'rectangle' objects-radiobutton.
##+#####################################################################

proc add_init_rect {x y} {

   global rectXinit rectYinit rect_filloutboth rectDrawOpts \
      COLOR1hex COLOR2hex curRectID

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]
   
   set rectXinit $x
   set rectYinit $y

   ## Set '-fill' and '-outline' parms in var rectDrawOpts.

   set rectDrawOpts ""
   if { "$rect_filloutboth" == "fill"} {
      set rectDrawOpts "-fill $COLOR1hex"
   } elseif { "$rect_filloutboth" == "outline"} {
      set rectDrawOpts "-outline $COLOR2hex"
   } else {
      set rectDrawOpts "-fill $COLOR1hex -outline $COLOR2hex"
   }

   ## Unset the var curRectID so that we do not delete an existing
   ## rectangle with the canvas 'delete $curRectID' command in the
   ## proc 'add_drag_rect'.

   # catch {unset $curRectID}
   set curRectID ""
}
## END OF proc 'add_init_rect'


##+#####################################################################
## proc 'add_drag_rect'
## PURPOSE:  Draws a rectangle on the canvas. Gets start point
##           from global vars rectXinit rectYinit.
##           Gets end point from xy passed into this proc, adjusted
##           for canvas extent.
##   Keeps deleting and re-drawing rectangles according to button1-motion.
##
## CALLED BY: button1-motion binding on the canvas. The add-rectangle bindings
##            are setup via a click on the 'rectangle' objects-radiobutton.
##+#####################################################################

proc add_drag_rect {x y} {

   global rectXinit rectYinit rectDrawOpts curRectID lineWidthPx rect_equi0or1
   ## rect_filloutboth, COLOR1hex, and COLOR2hex were used to set rectDrawOpts.
   ## They are no longer needed in this global statement.


   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   ## Delete previous rectangle, if any, and (re)draw the rectangle,
   ## including setting a tag, to help decrement counts in proc 'delete_object'.

   catch {.fRcanvas.can delete $curRectID}

   ## Adjust the current y coordinate to make the rectangle square,
   ## if equilateral was requested.

   if {$rect_equi0or1 == 1} {
      set ynew [expr {$rectYinit + ($x - $rectXinit)}]
   } else {
      set ynew $y
   }
   
   set curRectID [eval .fRcanvas.can create rectangle \
      $rectXinit $rectYinit $x $ynew \
      -width $lineWidthPx $rectDrawOpts \
      -tags TAGrect]

}
## END OF proc 'add_drag_rect'


##+#####################################################################
## proc 'add_end_rect'
## PURPOSE:  Increments rectangle count, because button1-release
##           indicates recttangle draws (via proc 'add_drag_rect') are done.
##
## CALLED BY: button1-release binding on the canvas. The add-rect bindings
##            are setup via a click on the 'rectangle' objects-radiobutton. 
##+#####################################################################

proc add_end_rect {x y} {

   global Nrects

   incr Nrects

   ## Update counts in the status frame label widget.

   update_info_label

}
## END OF proc 'add_end_rect'


##+##################################################################
## proc  'add_text_line'
##+##################################################################
## PURPOSE: Puts a text-line on the canvas (in the upper left corner)
##          based on the text string currently in the
##          text-line-entry-field.
##
##          (The user can move the image where wanted. We may change
##           this to accept xy location as args to this proc.)
##          
##          Also augments the Ntxtlines count.
##
## CALLED BY:  two bindings on the text-entry-field ---
##             one binding for Return-key and one for button1-release.
##            (We may change this to a button1-release binding on
##             the canvas, with xy loc passed as args.)
##+##################################################################

proc add_text_line {} {

   global Ntextlines COLOR2hex curFONTspecs ENTRYtext

   if {"$ENTRYtext" == ""} {return}

   .fRcanvas.can create text 0 0 -anchor nw \
      -fill $COLOR2hex -font "$curFONTspecs" \
      -text "$ENTRYtext" -tag TAGtextline

   incr Ntextlines

   ## Update counts in the status frame label widget.

   update_info_label
}
## END OF PROC  'add_text_line'


##+#####################################################################
## proc 'clear_canvas'
## PURPOSE:  Clears all objects off of the canvas.
##
## CALLED BY:  the '-command' option on the 'Clear' button.
##+#####################################################################

proc clear_canvas {} {

   .fRcanvas.can delete all
}
## END OF proc 'clear_canvas'


##+##################################################################
## proc  'delete_object'
##+##################################################################
## PURPOSE: Deletes an object (rectangle, oval, diamond, line, curve,
##          text, image, whatever is detected by 'find closest')
##          nearest the current cursor position.
##
##          Also decrements the appropriate count and calls
##          proc 'update_info_label' to display new counts.
##
## CALLED BY:  bind .fRcanvas.can <ButtonRelease-3>
##+##################################################################

## We set this var in GUI-init section at bottom of this script.
# set pixelTol 3

proc delete_object {x y} {

   global pixelTol Nrects Novals Ndiamonds Nlines Ncurves Ntextlines Nimages
   ## Npolys not implemented, yet.

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   ## Find canvas object nearest $x $y. This returns the 'last one'
   ## (uppermost) in the display list.

   set objID [.fRcanvas.can find closest $x $y $pixelTol]

   ## We could popup a prompt to the user here indicating the
   ## item that will be deleted and ask the user if it is OK
   ## to do the delete.

   ## Get the object tag(s), to determine which count to decrement.

   set objTAGs [.fRcanvas.can gettags $objID]

   ## We delete the object.

   .fRcanvas.can delete $objID

   ## FOR TESTING:
   #    puts "'delete_object' >    objID: $objID   objTAGs: $objTAGs"

   ## objTAGs typically contains TWO tags. For example, if the object
   ## detected was a 'line':
   ##    'TAGline current' when a line-object is detected by 'find closest'


   ## We decrement the appropriate count --- rectangle, oval, diamond,
   ## line, curve, text, or image.

   if { $objTAGs == "TAGrect current" || $objTAGs == "TAGrect" } {
      incr Nrects -1
   }

   if { $objTAGs == "TAGoval current" || $objTAGs == "TAGoval" } {
      incr Novals -1
   }

   if { $objTAGs == "TAGdiamond current" || $objTAGs == "TAGdiamond" } {
      incr Ndiamonds -1
   }

   if { $objTAGs == "TAGline current" || $objTAGs == "TAGline" } {
      incr Nlines -1
   }

   if { $objTAGs == "TAGcurve current" || $objTAGs == "TAGcurve" } {
      incr Ncurves -1
   }

   # if { $objTAGs == "TAGpoly current" || $objTAGs == "TAGpoly" } {
   #    incr Npolys -1
   # }

   if { $objTAGs == "TAGtextline current" || $objTAGs == "TAGtextline" } {
      incr Ntextlines -1
   }

   if { $objTAGs == "TAGimg current" || $objTAGs == "TAGimg" } {
      incr Nimages -1
   }

   ## Update counts in the status frame label widget.

   update_info_label
}
## END OF proc delete_object


##+#########################################################################
## Proc 'get_img_filename' -
##
##      To get the name of an image file (GIF/PNG) and put the
##      filename into global var 'ENTRYfilename'.
##
## Used by: the '-command' option of the 'Browse ...' button.
##+#########################################################################

set curDIR "$env(HOME)"

## FOR TESTING:
   set curDIR "pwd"

proc get_img_filename {} {

   global ENTRYfilename env curDIR IDimg1 Nimages

   ## Load data from an OBJ file

   set fName [tk_getOpenFile -parent . -title "Select GIF/PNG file to load" \
            -initialdir "$curDIR" ]

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

   if {[file exists $fName]} {
      set ENTRYfilename "$fName"
      set curDIR [ get_chars_before_last / in "$ENTRYfilename" ]
      
      # image create photo IDimg1 -file "$ENTRYfilename"
      set IDimg1 [image create photo -file "$ENTRYfilename"]

      ## FOR TESTING:
      #  puts "get_img_filename - IDimg1: $IDimg1"

      .fRcanvas.can create image 0 0 -anchor nw -image $IDimg1 -tag TAGimg

      incr Nimages

      update_info_label

      # catch {.fRcanvas.can raise TAGlines}
      # .fRcanvas.can raise TAGlines
      # .fRcanvas.can lower $IDimg1
      # .fRcanvas.can lower TAGimg
   }
}
## END OF proc 'get_img_filename'


## Proc 'get_chars_before_last' -
## INPUT:  A character and a string.
##         Note: The "in" parameter is there only for clarity.
##
## OUTPUT: Returns all of the characters in the string "strng" that
##         are BEFORE the last occurence of the characater "char".
##
## EXAMPLE CALL: To extract the directory from a fully qualified file name:
##
## set directory [ get_chars_before_last "/" in "/home/abc01/junkfile" ]
##
##      $directory will now be the string "/home/abc01"
##

proc get_chars_before_last { char in strng } {

   set end [ expr [string last $char $strng ] - 1 ]
   # set start 0
   # set output [ string range $strng $start $end ]
   set output [ string range $strng 0 $end ]

   ## FOR TESTING:
   # puts "From 'get_chars_before_last' proc:"
   # puts "STRING: $strng"
   # puts "CHAR: $char"
   # puts "RANGE up to LAST CHAR - start: 0   end: $end"

   return $output

}
## END OF 'get_chars_before_last' PROCEDURE


##+#########################################################
## proc  'move_grab'
##+#########################################################
## PURPOSE: Determines an object-ID to move.
##          Gets the ID of a 'nearest-object' on the canvas
##          to the button-press location.
##          Sets the current xy in vars prevX and prevY.
##
## CALLED BY: bind .fRcanvas.can <ButtonPress-2>
##+#########################################################

proc move_grab {x y} {

   global pixelTol moveID prevX prevY
   # prevX prevY

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   set prevX $x
   set prevY $y

   ## Find canvas object nearest $x $y. This returns the 'last one'
   ## (uppermost) in the display list.

   set moveID [.fRcanvas.can find closest $x $y $pixelTol]

   ## FOR TESTING:
   # set objTAGs [.fRcanvas.can gettags $objID]
   # puts "'move_object' >  objID: $objID   objTAGs: $objTAGs"

}
## END OF PROC  'move_grab'


##+#########################################################
## proc  'move_object'
##+#########################################################
## PURPOSE: Moves an object on the canvas whose ID is
##          in global var $moveID.
##
## CALLED BY: bind .fRcanvas.can <Button2-Motion>
##+#########################################################

proc move_object {x y} {

   global pixelTol moveID prevX prevY

   ## A precaution.
   # if {"$moveID" == ""} {return}

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasy $y]

   ## FOR TESTING:
   # set objTAGs [.fRcanvas.can gettags $moveID]
   # puts "'move_object' >  moveID: $moveID   objTAGs: $objTAGs"

   ## Reset the location of the object on the canvas, by $moveID.

   .fRcanvas.can move $moveID [expr {$x - $prevX}] [expr {$y- $prevY}]
   set prevX $x
   set prevY $y

   ## FOR TESTING:
   # puts "'move_object' > Moving object $moveID to $x $y"

}
## END OF PROC  'move_object'


##+#########################################################
## proc  'move_end'
##+#########################################################
## PURPOSE: Empties the global var $moveID.
##
## CALLED BY: bind .fRcanvas.can <ButtonRelease-2>
##+#########################################################

proc move_end {x y} {

   global moveID

   set moveID ""

}
## END OF PROC  'move_end'


##+#####################################################################
## 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 tagged items (lines) lie.
##
## Arguments: none
##
## CALLED BY:  .fRleft.fRbuttons.buttCOLORbkgd  button
##+#####################################################################

proc set_background_color {} {

   global COLORbkgdr COLORbkgdg COLORbkgdb COLORbkgdhex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLORbkgdr: $COLORbkgdr"
   #    puts "COLORbkgdg: $COLORbkgdg"
   #    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

   ## Set the color of the canvas.

   .fRcanvas.can config -bg $COLORbkgdhex

   update_colors_label

}
## END OF proc 'set_background_color'


##+#####################################################################
## proc  'set_font'
##+##################################################################### 
## PURPOSE:  This procedure is invoked to get a font to
##           use for the next text-line that is created on the canvas.
##
##           The FE Font Selector GUI is used to return the
##           6-components of a font specification.
##
## ARGUMENTS: none
##
## OUTPUT:   in global var  'curFONTspecs'
##           (and in font-name   'fontTEMP_text'?)
##
## CALLED BY:  .fRbuttons.buttFONT  button
##+#####################################################################

## Initialize the global curFONTspecs var.
## We do this in the GUI-init section at the bottom of this script.
##
# set curFONTspecs  [ font actual fontTEMP_varwidth ]

proc set_font {} {

   global curFONTspecs
   # global feDIR_tkguis

   ## FOR TESTING:
   #   puts "set_font > curFONTspecs: $curFONTspecs"

   ## Start up a version of the FE font selector GUI with
   ## the current font specs passed as the default. Put the
   ## font specs that the user chooses in 'newFONTspecs'.

   set newFONTspecs [ exec \
      ./select_tkFont_standAlone.tk \
      $curFONTspecs ]

   #   ../SELECTORutils/select_tkFont_standAlone.tk \
   #   $feDIR_tkguis/select_tkFont_standAlone.tk \

   if { "$newFONTspecs" == ""} {
      return
   }

   ## FOR TESTING:
   #    puts "newFONTspecs: $newFONTspecs"

   if { "$newFONTspecs" == "" } { return }

   ## Save the chosen font specs for the next default.
   set curFONTspecs "$newFONTspecs"

   ## Update the font-for-next-text-line label.

   update_font_label

}
## END OF PROC 'set_font'


##+#####################################################################
## proc 'set_object_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:  .fRleft.fRbuttons.buttCOLOR1  button
##+#####################################################################

proc set_object_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_colors_label

}
## END OF proc 'set_object_color1'


##+#####################################################################
## proc 'set_object_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:  .fRleft.fRbuttons.buttCOLOR2  button
##+#####################################################################

proc set_object_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_colors_label

}
## END OF proc 'set_object_color2'


##+#########################################################
## proc  setup_opts-binds_for_objadd
##
## PURPOSE: Refills the '.fRobjopts' frame with
##          widgets suitable for creating rectangles.
##
## CALLED BY: button-1-release on the 'rectangle' radiobutton
##            in the '.fRobjects' frame.
##+#########################################################

proc setup_opts-binds_for_objadd {} {

   global curObject

   ## Remove the widgets/frames currently in frame '.fRobjopts'.

   foreach optWidget [pack slaves .fRobjopts] {
       pack forget $optWidget
   }

   ## Clear out the current Button1 bindings. Assure a fresh start.

   bind .fRcanvas.can <ButtonPress-1>   {}
   bind .fRcanvas.can <Button1-Motion>  {}
   bind .fRcanvas.can <ButtonRelease-1> {}


   #############################################################
   ## Pack the appropriate opt frame(s) in the 'fRobjopts' frame,
   ## and set the appropriate (Button1) bindings for adding the
   ## object to the canvas.
   #############################################################

   if {"$curObject" == "rect"} {
      pack .fRobjopts.fRrectopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRcanvas.can <ButtonPress-1>   {add_init_rect %x %y}
      bind .fRcanvas.can <Button1-Motion>  {add_drag_rect %x %y}
      bind .fRcanvas.can <ButtonRelease-1> {add_end_rect %x %y}
   }


   if {"$curObject" == "oval"} {
      pack .fRobjopts.fRovalopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRcanvas.can <ButtonPress-1>   {add_init_oval %x %y}
      bind .fRcanvas.can <Button1-Motion>  {add_drag_oval %x %y}
      bind .fRcanvas.can <ButtonRelease-1> {add_end_oval %x %y}
   }


   if {"$curObject" == "diamond"} {
      pack .fRobjopts.fRdiamondopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRcanvas.can <ButtonPress-1>   {add_init_diamond %x %y}
      bind .fRcanvas.can <Button1-Motion>  {add_drag_diamond %x %y}
      bind .fRcanvas.can <ButtonRelease-1> {add_end_diamond %x %y}
   }

   ## We do not implement polygons, yet.
   if {0} {
   if {"$curObject" == "poly"} {
      pack .fRobjopts.fRpolyopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRcanvas.can <ButtonPress-1>   {add_init_poly %x %y}
      bind .fRcanvas.can <Button1-Motion>  {add_drag_poly %x %y}
      bind .fRcanvas.can <ButtonRelease-1> {add_end_poly %x %y}
   }
   }
   ## END OF    if {0}

   if {"$curObject" == "line"} {
      pack .fRobjopts.fRlineopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRcanvas.can <ButtonPress-1>   {add_init_line %x %y}
      bind .fRcanvas.can <Button1-Motion>  {add_drag_line %x %y}
      bind .fRcanvas.can <ButtonRelease-1> {add_end_line %x %y}
   }

   if {"$curObject" == "curve"} {
      pack .fRobjopts.fRcurveopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRcanvas.can <ButtonPress-1>   {add_init_curve %x %y}
      bind .fRcanvas.can <Button1-Motion>  {add_points_to_curve %x %y}
      bind .fRcanvas.can <ButtonRelease-1> {add_end_curve %x %y}
   }

   if {"$curObject" == "text"} {
      pack .fRobjopts.fRtextopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRobjopts.fRtextopts1.entTEXT <Return>           { add_text_line }
      bind .fRobjopts.fRtextopts1.entTEXT <ButtonRelease-3>  { add_text_line }
      # bind .fRcanvas.can <ButtonRelease-1> {add_text_line %x %y}
   }


   if {"$curObject" == "img"} {
      pack .fRobjopts.fRimgopts1 \
         -side top \
         -anchor nw \
         -fill none \
         -expand 0
      bind .fRobjopts.fRimgopts1.entFILENAME <Return>           { add_image }
      bind .fRobjopts.fRimgopts1.entFILENAME <ButtonRelease-3>  { add_image }
      # bind .fRcanvas.can <ButtonRelease-1> {add_image %x %y}
   }


   ## We do not implement 'move' via Button1. We try Button2 first.
   if {0} {
   if {"$curObject" == "move"} {
      bind .fRcanvas.can <Button1-Motion>  {move_object %x %y}
   }
   }
   ## END OF  if {0}

}
## END OF proc 'setup_opts-binds_for_objadd'


##+#####################################################################
## 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 COLOR1hex COLOR2hex COLORbkgdhex \
      COLOR1r COLOR1g COLOR1b \
      COLOR2r COLOR2g COLOR2b \
      COLORbkgdr COLORbkgdg COLORbkgdb

#    .fRbuttons.labelCOLORS configure -text "\
# Current Background Color: $COLORbkgdhex    NEXT-TEXT COLOR: $COLOR1hex   NEXT-TEXT FONT:
#  $curFONTspecs"

   .fRbuttons.labelCOLORS configure -text "\
Next-Object:  Fill-Color - $COLOR1hex   Outline/Line/Text-Color - $COLOR2hex
 Background Color: $COLORbkgdhex"

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

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

   .fRbuttons.buttCOLORbkgd configure -bg $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  'update_font_label'
##+##################################################################### 
## PURPOSE:  Update the font specs in the label widget
##           '.fRobjopts.fRtextopts1.labelFONT'
##
## ARGUMENTS: global var curFONTspecs
##
## CALLED BY: the procs that add and delete objects (rect,oval,line,...)
##            on the canvas --- and by the GUI init section at the
##            bottom of this script.
##+#####################################################################

proc update_font_label {} {

   global curFONTspecs

   .fRobjopts.fRtextopts1.labelFONT configure -text "$curFONTspecs"

}
## END OF proc 'update_font_label'


##+#####################################################################
## proc  'update_info_label'
##+##################################################################### 
## PURPOSE:  Update the counts in the label widget
##           '.fRopts.labelINFO'
##
## ARGUMENTS: global count vars for about 7 object types
##
## CALLED BY: the procs that add and delete objects (rect,oval,line,...)
##            on the canvas --- and by the GUI init section at the
##            bottom of this script.
##+#####################################################################

proc update_info_label {} {

   global Nrects Novals Ndiamonds Nlines Ncurves Ntextlines Nimages
   ## global Npolys

   .fRopts.labelINFO configure -text "\
Rectangles: $Nrects   Ovals: $Novals   Diamonds: $Ndiamonds  Lines: $Nlines
 Curves: $Ncurves  Text-objects: $Ntextlines   Images: $Nimages"

}
## END OF PROC  'update_info_label'


##+#####################################################################
## proc  'zoom_object'
##+##################################################################### 
## PURPOSE:  Scales an object up or down in size.
##           The zooming can be done centered at the %x %y coords,
##           using the canvas 'scale' command.
##
## ARGUMENTS: %D (a positive or negative number from the mouse wheel)
##            and %x %y from the mouse
##
## CALLED BY: a <MouseWheel> binding on the canvas.
##            bind .fRcanvas.can <MouseWheel>  {zoom_object %D %x %y}
##+#####################################################################

proc zoom_object {d x y} {

   # global

   ## Map from view coordinates to canvas coordinates, per
   ## page 559 of 4th edition of 'Practical Programming in Tcl & Tk'.
   set x [.fRcanvas.can canvasx $x]
   set y [.fRcanvas.can canvasx $y]

   ## Find canvas object nearest $x $y. This returns the 'last one'
   ## (uppermost) in the display list.

   set objID [.fRcanvas.can find closest $x $y $pixelTol]

   ## FOR TESTING:
   # set objTAGs [.fRcanvas.can gettags $objID]
   # puts "'zoom_object' >  objID: $objID   objTAGs: $objTAGs"
   # puts "'zoom_object' >  %D: $d   %x: $x   %y: $y"

   ## Scale the object according to the sign of $d.

   if {$d > 0} {
      .fRcanvas.can scale $objID $x $y 1.1 1.1
   }

   if {$d < 0} {
      .fRcanvas.can scale $objID $x $y 0.9 0.9
   }

}
## END OF PROC  'zoom_object'


##+########################################################################
## PROC 'popup_msg_var_scroll'
##+########################################################################
## PURPOSE: Report help or error conditions to the user.
## 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_msg_var_scroll { VARtext } {

   ## global fontTEMP_varwidth fontTEMP_fixedwidth
   ## Not needed. 'wish' makes the font-names 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 .topmsg}
   toplevel  .topmsg

   # wm geometry .topmsg 600x400+100+50

   wm geometry .topmsg +100+50

   wm title     .topmsg "Note"
   # wm title   .topmsg "Note to $env(USER)"

   wm iconname  .topmsg "Note"


   #####################################
   ## In the '.topmsg' frame -
   ## DEFINE a TEXT WIDGET with SCROLLBARs
   ## and an OK BUTTON.
   #####################################

   text .topmsg.text \
      -wrap none \
      -font fontTEMP_fixedwidth \
      -width  $VARwidth \
      -height $VARheight \
      -bg "#f0f0f0" \
      -relief raised \
      -bd 2 \
      -yscrollcommand ".topmsg.scrolly set" \
      -xscrollcommand ".topmsg.scrollx set"

   scrollbar .topmsg.scrolly \
                 -orient vertical \
      -command ".topmsg.text yview"

   scrollbar .topmsg.scrollx \
                -orient horizontal \
                -command ".topmsg.text xview"

   button .topmsg.butt \
      -text "OK" \
      -font fontTEMP_varwidth \
      -command  "destroy .topmsg"


   ###############################################
   ## Pack ALL the widgets in the '.topmsg' frame.
   ###############################################

   ## Pack the OK-button at the bottom of the frame,
   ## BEFORE packing the scrollbars and the text widget.

   pack  .topmsg.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 .topmsg.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 .topmsg.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 .topmsg.text \
      -side top \
      -anchor center \
      -fill both \
      -expand 1


   #####################################
   ## LOAD MSG INTO TEXT WIDGET.
   #####################################

   ##  .topmsg.text delete 1.0 end
 
   .topmsg.text insert end $VARtext
   
   .topmsg.text configure -state disabled
  
}
## END OF PROC 'popup_msg_var_scroll'


set HELPtext "\
**********  HELP for the WheeeDiagram Utility *****************

 Basically, this utility
 - ADDS objects with MB1 (mouse button 1),
 - MOVES objects with MB2
 - DELETES objects with MB3.

 That may be all you need to know.

 You can tell from the radiobuttons on the GUI that:

 You can draw rectangles, ovals, diamonds, lines, and curves
 on the canvas (via mouse actions) --- and you can place text
 and images on the canvas (via entry fields).  Use the 'Add:'
 radiobuttons to determine which objects to add.

 ADDING objects is done with MB1 (Mouse Button 1), for most
 objects. Choose an object type with the 'Add:' radiobuttons.
 Then press MB1 to establish a starting point.

 Drag MB1 to 'fill out' the object --- rectangle, oval,
 diamond, line, or curve. Release MB1 to finish off an object
 and see the object count incremented.

 After an object is added, you can MOVE it with MB2:  Press
 MB2 down when the mouse cursor is over an object. Keep
 pressing MB2 and move the mouse to drag the object where
 it is needed. Release MB2 to end the move.

 Once an object is added, you can DELETE it with MB3:  Press
 MB3 down when the mouse cursor is over the object. Release
 MB3 to delete the object. Object count is decremented.

 ---

 Currently text-lines and images are placed at the top-left
 corner of the canvas --- after pressing the Enter key when
 'in' the text or image-filename entry field. Alternatively,
 MB3-click on the entry field to place the object on the
 canvas.

 You can drag text and images to where you want them.
 (A future release may use MB1 to place text and images
 --- like the other objects.)

 Currently text is added a line at a time. Drag the lines
 into place --- one line under another. (In a future release,
 multi-line text entry may be 'enhanced'. The initial purpose
 of this utility was to make geometric drawings with short
 text entries of one or two characters, for the most part.)
"


##+#####################################################
## Additional GUI initialization, if needed (or wanted).
##+#####################################################

## Initialize the current diagramming 'fill/line/text' color.

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

## Initialize the current diagramming 'outline' color.

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


## Initialize the background color for the canvas.

# set COLORbkgdr 0
# set COLORbkgdg 0
# set COLORbkgdb 0
set COLORbkgdr 255
set COLORbkgdg 255
set COLORbkgdb 255
set COLORbkgdhex \
    [format "#%02X%02X%02X" $COLORbkgdr $COLORbkgdg $COLORbkgdb]

## Show the hex colors in a label.

update_colors_label

## Initialize the background/canvas color.

.fRcanvas.can config -bg $COLORbkgdhex

## Initialize counts and put them in the 'info' label.

set Nrects 0
set Novals 0
set Ndiamonds 0
# set Npolys 0
set Nlines 0
set Ncurves 0
set Ntextlines 0
set Nimages 0

update_info_label


## Initialize scale variable.

set lineWidthPx 2


## Initialize object radiobuttons.

set curObject "rect"
setup_opts-binds_for_objadd


## Initialize various object variables.

set LINEarrow "aro2nd"
set CURVEarrow "aronone"

## Set nearness var for delete & move object procs.

set pixelTol 3


## Initialize var 'curFONTspecs', used by the 'set_font' proc.
##    See the font-section near the top of the code
##    for some other font family names, such as
##    { new century schoolbook }

set curFONTspecs  [list -family {Liberation Mono} -size -20 \
   -weight bold -slant roman -underline 0 -overstrike 0]

update_font_label


Image Capture

When I want to put a 'wheeeDiagram' image into an image file, here is the sequence of steps that I follow.
   0) I do a screen capture of the 'wheeeDiagram' GUI with the 'gnome-screenshot' utility,
      on Linux.  The captured image is in a PNG file

   1) I use the 'mtpaint' image-editor program (on Linux) to crop the canvas image from
      the GUI window capture. I save the cropped image as a PNG file.

   3) I have found the PNG files from 'mtpaint' to be rather large. So I typically
      use one of my 'feNatuilusScripts', which uses the ImageMagick command 'convert',
      with '-quality 100', to make a '.jpg' file from the '.png' file.

Some Next Steps

With any canvas-drawing utility like this there are always many enhancements that can be added. I mentioned a few in the Help text above.

Even given the current, 'first-version' state of 'wheeDiagram', hopefully, you can see, from the images above, that there is a wealth of diagram/drawing types that you can make by using this rather simple 'wheeeDiagram' Tk script --- 'simple' compared to commercial diagramming software like the old 'Visio' software and various Adobe products. Simple and free --- what's not to like?

After the experience I gained with making 'rubber-banding' images of rectangles, ovals, diamonds, and lines as they are stretched into final size, I now have the coding experience to go back and make some enhancements to A 'Sketch On' GUI ... for drawing on an image or colored background --- some enhancements that I mentioned at the bottom of that page as items on my things-to-do list.

But first, I have some 3D projects and plotting projects to attend to.

In closing, I want to point out that I use the name 'wheeeDiagram' for this utility, rather than a first-person name like 'iDiagram', because .... Apple, there is no 'i' in 'team'.