[uniquename] - 2013dec03 I am interested in providing Tk GUI's that demonstrate various mathematical 'truths' in an animated and/or interactive fashion. Near the bottom of my 'bio' page [uniquename], I have listed about 30-plus math demos (geometry, number theory, sequences-and-series, etc.) that I would eventually like to complete (in my old age). On this page, I present the first of my 'geometry-fact demos' --- and in the process I have devised a set of 'draw' procs that will help me do drawings in 'world coordinates' --- and handle the conversion to pixels, on a Tk canvas, 'in the background'. (See the 'Some Features of the Code' section below for some explanation of this 'hiding-of-the-pixel-calcs' technique.) I have chosen as a first 'geometry demo' a classic geometry theorem attributed to Thales [http://en.wikipedia.org/wiki/Thales], circa 550 B.C. This theorem is often cited as one of the first known 'generalized' proofs in plane geometry. Thales is believed to have been a giant on whose shoulders Pythagoras, Euclid, and Archimedes stood. ------ '''Goals of the GUI''' In this Tk script, I wanted to demonstrate the Thales theorem [http://en.wikipedia.org/wiki/Thales%27_Theorem] that says: The triangles inscribed in a semi-circle are all RIGHT triangles --- when one side of the triangles is coincident with the flat, diametral side of the semi-circle. I wanted the GUI to use a Tk 'canvas' widget to show the semi-circle and a triangle inscribed within it. A 'scale' widget on the GUI may be used to allow the user to easily sweep through a family of triangles inscribed in the semi-circle. In this implementation, I decided to display the diametral side of the semi-circle on the bottom side of the semi-circle, and the 'peak point' of the triangle(s) is to move along the arc of the semi-circle while the other two vertices of the triangle stay fixed at the ends of the diametral side (straight line-segment) of the semi-circle. An additional goal of this GUI is to allow the canvas (and the geometry drawn within the canvas) to be easily resized if the GUI window is resized by the user. '''Additional items to display on the GUI''' Various numeric properties of the triangle could be displayed as the 'peak point' is moved along the circumference of the semi-circle. I decided to show the circumference of the triangle (sum of the lengths of the 3 sides) and the area of the triangle --- in a couple of 'label' widgets on the GUI. ------ '''Some Images of the GUI''' Here are a couple of images of the GUI that I ended up with. The first image shows the GUI as it first appears. [thalesTheorem_trianglesInSemiCircle_90deg_whiteONblue_screenshot_528x420.jpg] And here is an image after: 1) changing the 'draw' and 'background' colors of the canvas items and the canvas, 2) resizing the window to a smaller size than the initial size, and 3) sliding the 'slider' of the 'scale' widget to a different setting from the initial setting. [thalesTheorem_triangleInSemiCircle_21deg_yellowONblack_screenshot_488x317.jpg] Note that there are 2 color buttons across the top of the GUI for assigning a color to canvas background and to the items drawn on the canvas. Also note that there is a 'scale' widget on the GUI that allows the user to easily move the 'peak point' of the triangle(s) from one side of the semi-circle to the other. The size of the semi-circle and triangle can be changed by resizing the window and clicking on the 'Redraw' button. The new size of the canvas widget is used to determine the new location and size of the semi-circle. (The jaggies on the straight lines and the semi-circle arc MAY be nearly invisible if you try this script on a 'retina display', with monitor resolution on the order of 2000x1500 pixels --- more than 150 pixels per inch.) --- Note that the color buttons call on a color-selector-GUI script to set those colors. You can make that color-selector Tk script by cutting-and-pasting the code from the page [A non-obfuscated color selector GUI] on this site. ------ '''The Code :''' Below is the code that produced this GUI. There are comments at the top of the code, in a section titled 'CAPTURING THE GUI IMAGE', that describe how one can 'capture' images produced by this GUI. I follow my usual 'canonical' structure for Tk code, for this Tk script: 0) Set general window & widget parms (win-name, win-position, win-color-scheme, fonts, widget-geometry-parms, win-size-control, text-array-for-labels-etc). 1a) Define ALL frames (and sub-frames, if any). 1b) Pack ALL frames and sub-frames. 2) Define & pack all widgets in the frames, frame by frame. Within each frame, define ALL the widgets. Then pack the widgets. 3) Define keyboard and mouse/touchpad/touch-sensitive-screen 'event' BINDINGS, if needed. 4) Define PROCS, if needed. 5) Additional GUI initialization (typically with one or more of the procs), if needed. This Tk coding structure is discussed in more detail on the page [A Canonical Structure for Tk Code --- and variations]. This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in other scripts (code re-use). I call your attention to step-zero. One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future. ------ '''Experimenting with the GUI''' As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus Tk scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', and '-expand' --- on all 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 I have found a good setting of the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various widgets of this GUI. In particular ... The 'canvas' widget expands/contracts appropriately when the window size is changed --- and button and label widgets stay fixed in size and relative-location as the window size is changed. If anyone wants to change the way the GUI configures itself as the main window size is changed, they can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various widgets --- to get the widget behavior that they want. Note: I have found that one can get some quite surprising self-changing movement of the slider-button on scale widgets if you allow a scale widget to expand and allow the window to expand. So you may want the scale widgets to stay fixed in length ('-fill none') and/or use '-expand 0' when packing scale widgets --- rather than using '-fill x -expand 1' with scale widgets and an expandable GUI window. ------ Additional experimentation: You might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families. In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing. I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well. ------ '''Some features of the code''' That said, here's the code --- with plenty of comments to describe what most of the code-sections are doing. The main routine is the 'Redraw' proc. But that proc uses a set of 'draw' procs that allow the coder to work mostly in 'world coordinates' rather than 'pixel coordinates'. The 'draw' procs have fairly descriptive names: mapping_for_wc2px Xwc2px Ywc2px draw_line_x1y1x2y2 draw_arc_x1y1_degStart_degExtent draw_text_x1y1_center draw_point_x1y1_radiusPx The input to all of these procs except the first one are 'world coordinates' rather than 'pixel coordinates'. And the only pixel coordinates input to the 'mapping_for_wc2px' proc are the pixel coordinates of the upper-left and lower-right corners of a rectangle on the canvas --- and those two points are usually taken to be the corners of the canvas --- (0,0) and (canvasWidthPx,canvasHeightPx). The pixel calculations (to translate world coordinates to pixel coordinates) are 'hidden' in the 'Xwc2px' and 'Ywc2px' procs. You can see the comments in the 'Redraw' proc for details on how the 'draw' procs are used to make the image on the canvas. A few other procs are: set_draw_color - for the 'DrawColor' button set_background_color - for the 'BackgroundColor' button update_button_colors - called by the set-color procs popup_msgVarWithScroll - called by the 'Help' button ------ 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 --- especially in the 'Redraw' proc and the various 'draw' procs, the code would look like 'too much monkey business to be involved in', to quote Chuck Berry. Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of 'news people' on various TV channels --- all of them repeating the same phrases that someone has provided to all of them. ------ <>Code for Tk script 'thalesTheorem_rightTriangles_inSemiCircle.tk' : ====== #!/usr/bin/wish -f ##+######################################################################## ## ## SCRIPT: thalesTheorem_rightTriangles_inSemiCircle.tk ## ## PURPOSE: This Tk script is meant to demonstrate the Thales theorem that ## says: The triangles inscribed in a semi-circle (with one side ## of the triangles coincident with the flat, diametral side of ## the semi-circle) are all RIGHT triangles. ## ## This GUI uses a Tk 'canvas' widget to show the semi-circle ## and a triangle inscribed within it. A 'scale' widget on the ## GUI allows the user to easily sweep through a family of ## triangles inscribed in the semi-circle. ## ## In this implementation, ## the diametral side of the semi-circle is displayed on the ## bottom side of the semi-circle, and the 'peak point' of the ## triangle(s) moves along the arc of the semi-circle while the ## other two vertices of the triangle stay fixed at the ends ## of the diametral side (line-segment) of the semi-circle. ## ## A goal of this GUI is to allow the canvas (and the geometry ## drawn within the canvas) to auto-resize if the GUI window ## is resized by the user. ## ##+###################################### ## ADDITIONAL ITEMS TO DISPLAY (optional): ## ## Various numeric properties of the triangle can be displayed as the ## 'peak point' is moved along the circumference of the semi-circle ## --- such as the 3 angles of the triangle (in degrees or radians), ## the circumference of the triangle (sum of the lengths of the ## 3 sides), the area of the triangle, etc. ## ## Furthermore, eventually, various geometric properties of the ## triangle could be displayed on the canvas. For example, the ## 'centroid' of the triangle could be drawn as a point inside ## the triangle, and the centroid would move (be erased and re-drawn) ## as the 'peak point' of the triangle is moved by the user. ## ## In fact, some extra lines and text could be drawn on the canvas ## to indicate Thales' proof of the fact (the theorem). ## ##+################ ## GUI DESCRIPTION: ## ## This script provides a Tk GUI with the following widgets. ## ## 1) There is an 'fRbuttons' frame to hold BUTTONS such as ## 'Exit' and 'Help' buttons --- as well as a 'Reset' ## button. ## ## There is a SCALE widget that allows the user to move the ## 'peak point' of the triangles along the circumference of ## the semi-circle. The scale widget may be used to control ## an angle --- say from 0 to 180, or 1 to 179. This number (angle) ## is used to determine the x,y coordinates of the 'peak point'. ## ## 2) There is an 'fRcanvas' frame to contain a CANVAS widgets that ## holds the drawing of the semi-circle and the triangle(s). ## ## 3) Also an 'fRinfo' frame may be used to hold some LABEL widgets, ## to show properties of the triangle(s), such as circumference ## (sum of the lengths of the 3 sides) and area. ## ##+################################## ## METHOD USED to update the drawing: ## ## A set of drawing procs are used convert 'world coordinates' to ## pixel coordinates of the Tk canvas. ## ## The diametral side of the semi-circle is defined with center ## at 0,0 in 'world coordinates' --- and the end-points of the ## diameter are set to -1,0 and 1,0 in 'world coordinates'. ## ## Thus the semi-circle has 'unit radius', and the 'peak point' of ## each triangle, calculated in 'world coordinates', is ## the end-point of a radus extending from 0,0 to the (upper) arc ## of the semi-circle. ## ## The drawing procs are used to convert the 'world coordinates' ## to pixel coordinates of the Tk canvas and to draw lines and ## arcs. Some drawing procs are also provided to put some text items ## on the canvas, such as labels for vertices or values of angles. ## ## The names and descriptions of the drawing procs are below. ## ##+####################### ## CAPTURING THE GUI IMAGE: ## ## A screen/window capture utility (like 'gnome-screenshot' ## on Linux) can be used to capture the GUI image in a PNG ## or GIF file, say. ## ## If necessary, an image editor (like 'mtpaint' on Linux) ## can be used to crop the window capture image. The image ## could also be down-sized --- say to make a smaller image ## suitable for use in a web page or an email. ## ##+####################################################################### ## 'CANONICAL' STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (win-name, win-position, win-color-scheme, ## fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack the frames. ## ## 2) Define & pack all widgets in the frames, frame by frame. ## After all the widgets for a frame are defined, pack them in the frame. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen 'event' ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or more of ## the procs), if needed. ## ##+################################# ## Some detail of the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : ## '.fRbuttons' - to contain 'Exit', 'Help', 'Redraw' buttons ## as well as a label-and-scale pair. ## ## '.fRinfo' - to contain a stack of label widgets ## ## '.fRcanvas' - to contain a canvas widget, which will display ## the semi-circle and triangle(s). ## ## 1b) Pack ALL frames. ## ## 2) Define & pack all widgets in the frames -- basically going through ## frames & their interiors in left-to-right, or top-to-bottom order. ## ## 3) Define BINDINGS: such as button1-release on the 'scale' widget ## ## 4) Define PROCS: ## ## 'mapping_for_wc2px' - to set up constants to be used in converting ## a 'world coordinate' to a 'pixel coordinate' on ## the Tk canvas. ## ## This proc takes the coordinates of an UpperLeft (UL) ## point and a LowerRight (LR) point --- in both ## 'world coordinates' and 'pixel coordinates' and ## sets some global variables to the used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit ## ## (This will generally include a fractional amount, ## i.e. it is not necessarily an integer.) ## ## At least eight numbers are input to this proc, as indicated by: ## ## mapping_for_wc2px ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY ## ## Generally, the 'wc' inputs may be floating point numbers, and the ## 'px' inputs will generally be (non-negative) integers. ## ## Example: ## mapping_for_wc2px -1.2 1.2 0 0 1.2 -0.2 $canvasWidthPx $canvasHeightPx ## ##---- Other Drawing procs: ## ## 'Xwc2px' - To convert an x world-coordinate to pixels, ## to be used by the following 'draw_*' procs. ## ## 'Ywc2px' - To convert a y world-coordinate to pixels, ## to be used by the following 'draw_*' procs. ## ## (Note: Conversion from 'world coordinates' to ## pixels is done within these 2 procs. The ## global variables, that were set by a call ## to the proc 'mapping_for_wc2px', are used ## to do the conversion.) ## ## 'draw_line_x1y1x2y2' - Given x1,y1 and x2,y2 in 'world coordinates', ## this proc draws a line on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the line.) ## (The lines drawn may be passed a tag, such as ## 'TAGline'.) ## ## 'draw_arc_x1y1_degStart_degExtent' ## - Given x1,y1 and angles degStart and degExtent ## (in degrees, as indicated), this proc draws ## an arc on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the arc.) ## (The arc drawn may be passed a tag, such as ## 'TAGarc'.) ## ## 'draw_text_x1y1_center' - Given x1,y1 and a text string, this proc ## this proc draws the text string on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the text.) ## (The text drawn may be passed a tag, such as ## 'TAGtext'.) ## ## 'draw_point_x1y1_radiusPx' - Given x1,y1 and a radius (in pixels), this ## proc draws a circle on the canvas, centered ## at x1,y1 and with the specified radius. ## Radius zero causes a single pixel to be drawn. ## (A hex-RGB color specification is also input, ## to specify the color of the filled circle ## --- or the single pixel.) ## (The point drawn may be passed a tag, such as ## 'TAGpoint'.) ## ##--------- End of 'draw' utility procs. ## ## 'Redraw' - This proc can be used to erase and redraw the ## lines,arcs,text,etc. on the canvas --- for example, ## after the user has resized the GUI window --- ## or after the scale widget slider is moved. ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ## ## ## 5) Additional GUI Initialization: ## - call 'Redraw' to put the drawing on the canvas, for a given ## initial setting (angle) of the Tk scale widget. ## ##+####################################################################### ## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala') ## ## $ wish ## % puts "$tcl_version $tk_version" ## ## showed ## 8.5 8.5 ## but this script should work in most previous 8.x versions, and probably ## even in some 7.x versions (if font handling is made 'old-style'). ##+####################################################################### ## MAINTENANCE HISTORY: ## Started by: Blaise Montandon 2013dec01 Started the basic code of the ## script based on my previous ## meters script ## meters_net_stats.tk ## which contained most of the Tk ## widgets that are needed. ## Changed by: Blaise Montandon 2013dec02 Started coding the 'draw' procs. ##+######################################################################## ##+###################################################### ## Set WINDOW TITLE and POSITION. ##+###################################################### wm title . "Thales Theorem demo - Triangles in SemiCircle" wm iconname . "Thales" wm geometry . +15+30 ##+###################################################### ## Set the COLOR SCHEME for the window and its widgets --- ## such as listbox and entry field background color. ##+###################################################### tk_setPalette "#f0f0f0" # set listboxBKGD "#ffffff" set entryBKGD "#ffffff" set scaleBKGD "#f0f0f0" ## Initialize the draw fill-color (COLORDRAW vars) and ## canvas/background color (COLORBKGD vars). ## ## Alternatively, these initial color settings could be moved ## to the additional-GUI-initialization' section at the bottom ## of this script. ## Light gray: # set COLORDRAWr 200 # set COLORDRAWg 200 # set COLORDRAWb 200 ## White: set COLORDRAWr 255 set COLORDRAWg 255 set COLORDRAWb 255 set COLORDRAWhex [format "#%02X%02X%02X" $COLORDRAWr $COLORDRAWg $COLORDRAWb] set drawRGB $COLORDRAWhex ## Dark gray: # set COLORBKGDr 60 # set COLORBKGDg 60 # set COLORBKGDb 60 ## Light Blue: set COLORBKGDr 90 set COLORBKGDg 90 set COLORBKGDb 255 set COLORBKGDhex [format "#%02X%02X%02X" $COLORBKGDr $COLORBKGDg $COLORBKGDb] ##+######################################################## ## DEFINE (temporary) FONT NAMES. ## ## We use a VARIABLE-WIDTH font for text on LABEL and ## BUTTON widgets. ## ## We use a FIXED-WIDTH font for LISTBOX lists, ## for Help-text in a TEXT widget, and for ## the text in ENTRY fields, if any. ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## CANVAS widget geom settings: set initCanWidthPx 300 set initCanHeightPx 300 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ## LABEL widget geom settings: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 ## BUTTON widget geom settings: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 ## ENTRY widget geom settings: set BDwidthPx_entry 2 ## SCALE widget geom parameters: set BDwidthPx_scale 2 set scaleThicknessPx 10 ##+###################################################################### ## Set a MIN-SIZE of the window (roughly). ## ## For WIDTH, allow for the min-width of the '.fRbuttons' frame. ## ## For HEIGHT, allow for the stacked frames: ## 2 chars high for the '.fRbuttons' frame, ## 2 chars high for the '.fRinfo' frame, ## at least 50 pixels high for the '.fRcanvas' frame. ##+##################################################################### ## FOR WIDTH: set minWidthPx [font measure fontTEMP_varwidth \ " Exit Help Redraw Color Color ChangeTriangle: "] ## We accomodate a label-and-scale pair --- ## add pixels for length of the scale widget, at least 100. ## ## We add some pixels to account for right-left-size of ## window-manager decoration (~8 pixels) and some pixels for ## frame/widget borders (~5 widgets x 4 pixels/widget = 20 pixels). set minWinWidthPx [expr {128 + $minWidthPx}] ## For HEIGHT --- for ## 2 char high for 'fRbuttons' ## 2 char high for 'fRinfo' (at least) ## 50 pixels high for 'fRcanvas' (at least) set charHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {4 * $charHeightPx}] ## Add about 50 pixels for height of the canvas ## AND add about 20 pixels for top-bottom window decoration -- ## and some pixels for top-and-bottom of frame/widget borders ## (~4 widgets x 4 pixels/widget = 16 pixels). set minWinHeightPx [expr {86 + $minWinHeightPx}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We may allow the window to be resizable. We pack the canvases ## (and the frames that contain them) with '-fill both -expand 1' ## so that the canvases can be enlarged by enlarging the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+############################################################## ## 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(buttonREDRAW) "Redraw" set aRtext(buttonCOLORdraw) "Draw Color" set aRtext(buttonCOLORbkgd) "Backgrnd Color" set aRtext(labelSCALE) "ChangeTriangle (move peak point):" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' , '.fRinfo' , '.fRcanvas' ## ## Sub-frames: none ##+################################################################ ## FOR TESTING: (to see size of frames as window is resized) # set BDwidth_frame 2 # set RELIEF_frame raised set BDwidth_frame 0 set RELIEF_frame flat frame .fRbuttons -relief $RELIEF_frame -bd $BDwidth_frame # frame .fRinfo -relief $RELIEF_frame -bd $BDwidth_frame frame .fRinfo -relief raised -bd 2 # frame .fRcanvas -relief $RELIEF_frame -bd $BDwidth_frame frame .fRcanvas -relief raised -bd 2 ##+############################## ## PACK the FRAMES. ##+############################## pack .fRbuttons \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRinfo \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvas \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+########################################################## ## The FRAMES ARE PACKED. START PACKING WIDGETS IN THE FRAMES. ##+########################################################## ##+########################################################## ## In FRAME '.fRbuttons' - ## DEFINE-and-PACK 'BUTTON' WIDGETS ## --- Exit, Help, Redraw --- and a LABEL-AND-SCALE widget pair ## (for moving the peak of the triangle along the arc of ## the semi-circle). ##+########################################################## button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext"} button .fRbuttons.buttREDRAW \ -text "$aRtext(buttonREDRAW)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {Redraw} button .fRbuttons.buttCOLORdraw \ -text "$aRtext(buttonCOLORdraw)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_draw_color" button .fRbuttons.buttCOLORbkgd \ -text "$aRtext(buttonCOLORbkgd)" \ -font fontTEMP_SMALL_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_background_color" ## Here is the LABEL-AND-SCALE pair for changing the triangle. label .fRbuttons.labelSCALE \ -text "$aRtext(labelSCALE)" \ -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 SCALEangle 90.0 scale .fRbuttons.scaleANGLE \ -from 0.1 -to 179.9 \ -resolution 0.1 \ -font fontTEMP_SMALL_varwidth \ -variable SCALEangle \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 180 \ -width $scaleThicknessPx ## Pack the widgets in frame '.fRbuttons'. pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttREDRAW \ .fRbuttons.buttCOLORdraw \ .fRbuttons.buttCOLORbkgd \ .fRbuttons.labelSCALE \ .fRbuttons.scaleANGLE \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## In FRAME '.fRinfo' - ## DEFINE-and-PACK TWO (or more) LABEL widgets. ##+####################################################### label .fRinfo.labelINFO1 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label label .fRinfo.labelINFO2 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Pack the widgets in frame '.fRinfo'. pack .fRinfo.labelINFO1 \ .fRinfo.labelINFO2 \ -side top \ -anchor nw \ -fill x \ -expand 0 ##+######################################################## ## In FRAME '.fRcanvas' - ## DEFINE-and-PACK a CANVAS WIDGET (no scrollbars). ## ## We highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+####################################################### canvas .fRcanvas.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 ## Pack the widgets in frame '.fRcanvas.fRcanvas1'. pack .fRcanvas.can \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+################################################## ## END OF DEFINITION of the GUI widgets. ##+################################################## ## Start of BINDINGS, PROCS, Added-GUI-INIT sections. ##+################################################## ##+################################################################## ##+################################################################## ## BINDINGS SECTION: button1-release on the 'scale' widget ##+################################################################## bind .fRbuttons.scaleANGLE {Redraw} ##+################################################################## ##+################################################################## ## DEFINE PROCS SECTION: ## ## 'mapping_for_wc2px' - to set up constants to be used in converting ## a 'world coordinate' to a 'pixel coordinate' on ## the Tk canvas. ## ## This proc takes the coordinates of an UpperLeft (UL) ## point and a LowerRight (LR) point --- in both ## 'world coordinates' and 'pixel coordinates' and ## sets some global variables to the used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit ## ## (This will generally include a fractional amount, ## i.e. it is not necessarily an integer.) ## ## At least eight numbers are input to this proc, as indicated by: ## ## ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY ## ## Generally, the 'wc' inputs may be floating point numbers, and the ## 'px' inputs will generally be (non-negative) integers. ## ## Example: ## -1.2 1.2 0 0 1.2 -0.2 $canvasWidthPx $canvasHeightPx ## ##-------- Drawing procs: ## ## 'Xwc2px' - Converts an x world-coordinate to pixel units, ## for the following 'draw' procs. ## ## 'Ywc2px' - Converts a y world-coordinate to pixel units, ## for the following 'draw' procs. ## ## 'draw_line_x1y1x2y2' - Given x1,y1 and x2,y2 in 'world coordinates', ## this proc draws a line on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the line.) ## (The lines drawn may be passed a tag, such as ## 'TAGline'.) ## ## (Note: Conversion from 'world coordinates' to ## pixels is done within this proc --- and in ## the following 'draw' procs. The global variables, ## that were set by a call to the proc ## 'mapping_for_wc2px', are used to do the ## conversion.) ## ## 'draw_arc_x1y1_degStart_degExtent' ## - Given x1,y1 and angles degStart and degExtent ## (in degrees, as indicated), this proc draws ## an arc on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the arc.) ## (The arc drawn may be passed a tag, such as ## 'TAGarc'.) ## ## 'draw_text_x1y1_center' - Given x1,y1 and a text string, this proc ## this proc draws the text string on the canvas. ## (A hex-RGB color specification is also input, ## to specify the color of the text.) ## (The text drawn may be passed a tag, such as ## 'TAGtext'.) ## ## 'draw_point_x1y1_radiusPx' - Given x1,y1 and a radius (in pixels), this ## proc draws a circle on the canvas, centered ## at x1,y1 and with the specified radius. ## Radius zero causes a single pixel to be drawn. ## (A hex-RGB color specification is also input, ## to specify the color of the filled circle ## --- or the single pixel.) ## (The point drawn may be passed a tag, such as ## 'TAGpoint'.) ## ##--------- End of 'draw' procs. ## ## 'Redraw' - This proc uses the draw procs above to ## erase and redraw the lines,arcs,text,etc. ## on the canvas --- for example, ## after the user has resized the GUI window. ## ## 'set_draw_color' - Sets the color for drawing elements on the canvas. ## ## 'set_background_color' - Sets the color for the canvas (background). ## ## 'update_button_colors' - Sets the color of the 2 color buttons. ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ## ##+################################################################# ##+######################################################################## ## PROC 'mapping_for_wc2px' ##+######################################################################## ## PURPOSE: Sets up 'constants' to be used in converting ## a 'world coordinate' to a 'pixel coordinate' on ## the Tk canvas. ## ## METHOD: This proc takes the coordinates of an UpperLeft (UL) ## point and a LowerRight (LR) point --- in both ## 'world coordinates' and 'pixel coordinates' and ## sets some global variables to the used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit ## ## (This will generally include a fractional amount, ## i.e. it is not necessarily an integer.) ## ## INPUTS: ## ## At least eight numbers are input to this proc, as indicated by: ## ## ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY ## ## Generally, the 'wc' inputs may be floating point numbers, and the ## 'px' inputs will generally be (non-negative) integers. ## ## Example: ## -1.2 1.2 0 0 1.2 -0.2 $canvasWidthPx $canvasHeightPx ## ## CALLED BY: by the Redraw' proc. ##+######################################################################## proc mapping_for_wc2px {xORy ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY} { global PXperWC BASEwcX BASEwcY BASEpxX BASEpxY adjustYpx ## FOR TESTING: (to dummy out this proc) # return ############################################################ ## Calculate PXperWC. ############################################################ set PXperWCx [expr {abs(($LRpxX - $ULpxX) / ($LRwcX - $ULwcX))}] set PXperWCy [expr {abs(($LRpxY - $ULpxY) / ($LRwcY - $ULwcY))}] ## FOR TESTING: if {0} { puts "proc 'Redraw':" puts "LRwcY: $LRwcY ULwcY: $ULwcY LRwcX: $LRwcX ULwcX: $ULwcX" puts "PXperWCx: $PXperWCx" puts "LRpxY: $LRpxY ULpxY: $ULpxY LRpxX: $LRpxX ULpxX: $ULpxX" puts "PXperWCy: $PXperWCy" } if {$PXperWCx > $PXperWCy} { set PXperWC $PXperWCy } else { set PXperWC $PXperWCx } ## Alternate method - let the 'xORy' var specify which ratio we ## want to use: # if {$xORy == "x"} { # set PXperWC $PXperWCx # } else { # set PXperWC $PXperWCy # } ############################################################ ## In case the pixels are not square, provide a factor ## that can be used to adjust in the Y direction. ############################################################ set adjustYpx 1.0 ############################################################ ## Set BASEwcX, BASEwcY, BASEpxX and BASEpxY. ############################################################ set BASEwcX $ULwcX set BASEwcY $ULwcY set BASEpxX $ULpxX set BASEpxY $ULpxY ## FOR TESTING: if {0} { puts "proc 'mapping_for_wc2px':" puts "PXperWC: $PXperWC" puts "BASEwcX: $BASEwcX BASEwcY: $BASEwcY" puts "BASEpxX: $BASEpxX BASEpxY: $BASEpxY" } } ## END OF proc 'mapping_for_wc2px' ##+######################################################################## ## PROC 'Xwc2px' ##+######################################################################## ## PURPOSE: Converts an x world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Xwc2px {x} { global PXperWC BASEwcX BASEwcY BASEpxX BASEpxY adjustYpx set px [expr {($x - $BASEwcX) * $PXperWC + $BASEpxX}] return $px } ## END OF proc 'Xwc2px' ##+######################################################################## ## PROC 'Ywc2px' ##+######################################################################## ## PURPOSE: Converts an y world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Ywc2px {y} { global PXperWC BASEwcX BASEwcY BASEpxX BASEpxY adjustYpx set px [expr {($BASEwcY - $y) * $PXperWC * $adjustYpx + $BASEpxY}] return $px } ## END OF proc 'Ywc2px' ##+######################################################################## ## PROC 'draw_line_x1y1x2y2' ##+######################################################################## ## PURPOSE: Given x1,y1 and x2,y2 in 'world coordinates', ## this proc draws a line on the canvas. ## ## (A hex-RGB color specification is also input, ## to specify the color of the line.) ## ## (The lines drawn may be passed a tag, such as ## 'TAGline'.) ## ## (Note: Conversion from 'world coordinates' to ## pixels is done within this proc --- and in ## the following 'draw' procs. The global variables, ## that were set by a call to the proc ## 'mapping_for_wc2px', are used to do the ## conversion.) ## ## CALLED BY: proc 'Redraw' ##+####################################################################### proc draw_line_x1y1x2y2 {x1 y1 x2 y2 hexRGB tagID} { global lineWidthPx ## FOR TESTING: (to dummy out this proc) # return ############################################### ## Convert world-coords to pixels. ############################################### set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] ## FOR TESTING: if {0} { puts "proc 'draw_line_x1y1x2y2':" puts "x1: $x1 y1: $y1 x1Px: $x1Px y1Px: $y1Px" puts "x2: $x2 y2: $y2 x2Px: $x2Px y2Px: $y2Px" } ##################################### ## Draw the line. ##################################### .fRcanvas.can create line \ $x1Px $y1Px $x2Px $y2Px \ -fill $hexRGB -width $lineWidthPx -tags $tagID } ## END OF proc 'draw_line_x1y1x2y2' ##+######################################################################## ## PROC 'draw_arc_x1y1_degStart_degExtent' ##+######################################################################## ## PURPOSE: Given x1,y1 and angles degStart and degExtent ## (in degrees, as indicated), this proc draws ## an arc on the canvas --- centered at x1,y1 ## and with a specified radius. ## ## (A hex-RGB color specification is also input, ## to specify the color of the arc.) ## ## (The arc drawn may be passed a tag, such as ## 'TAGarc'.) ## ## CALLED BY: proc 'Redraw' ##+######################################################################## proc draw_arc_x1y1_degStart_degExtent {x1 y1 radius degStart degExtent hexRGB tagID} { global PXperWC lineWidthPx radsPERdeg ## FOR TESTING: (dummy out this proc) # return ####################################################### ## Convert the radius from world coord units to pixels. ####################################################### set radiusPx [expr {$radius * $PXperWC}] ############################################### ## Convert x1,y1 world-coord units to pixels. ############################################### set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] ## FOR TESTING: if {0} { puts "proc 'draw_arc_x1y1_degStart_degExtent':" puts "x1: $x1 y1: $y1 x1Px: $x1Px y1Px: $y1Px" } ################################################################## ## Set the corner coords for the circle that defines the arc. ################################################################## set topleftXpx [expr {$x1Px - $radiusPx}] set topleftYpx [expr {$y1Px - $radiusPx}] set botrightXpx [expr {$x1Px + $radiusPx}] set botrightYpx [expr {$y1Px + $radiusPx}] ######################################################## ## Draw the arc. ######################################################## .fRcanvas.can create arc \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ -start $degStart -extent $degExtent \ -style arc -outline $hexRGB -width $lineWidthPx -tag $tagID } ## END OF proc 'draw_arc_x1y1_degStart_degExtent' ##+######################################################################## ## PROC 'draw_text_x1y1_center' ##+######################################################################## ## PURPOSE: Given x1,y1 and a text string, this proc ## this proc draws the text string on the canvas. ## ## (A hex-RGB color specification is also input, ## to specify the color of the text.) ## ## (The text drawn may be passed a tag, such as ## 'TAGtext'.) ## ## CALLED BY: proc 'Redraw' ##+######################################################################## proc draw_text_x1y1_center {x1 y1 hexRGB tagID textSTRING} { # global lineWidthPx ## FOR TESTING: (dummy out this routine) # return ############################################### ## Convert x1,y1 world-coord units to pixels. ############################################### set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] ## FOR TESTING: if {0} { puts "proc 'draw_text_x1y1_center':" puts "x1: $x1 y1: $y1 x1Px: $x1Px y1Px: $y1Px" } ###################################### ## Draw the text string. ###################################### .fRcanvas.can create text \ $x1Px $y1Px \ -anchor center -justify center -fill $hexRGB \ -text $textSTRING -font fontTEMP_SMALL_varwidth } ## END OF proc 'draw_text_x1y1_center' ##+######################################################################## ## PROC 'draw_point_x1y1_radiusPx' ##+######################################################################## ## PURPOSE: Given x1,y1 and a radius (in pixels), this ## proc draws a circle on the canvas, centered ## at x1,y1 and with the specified radius. ## ## Radius zero causes a single pixel to be drawn. ## ## (A hex-RGB color specification is also input, ## to specify the color of the filled circle ## --- or the single pixel.) ## ## (The point drawn may be passed a tag, such as ## 'TAGpoint'.) ## ## CALLED BY: the 'Redraw' proc ##+######################################################################## proc draw_point_x1y1_radiusPx {x1 y1 radiusPx hexRGB tagID} { global lineWidthPx drawRGB ## FOR TESTING: (dummy out this routine) # return ############################################### ## Convert world-coords to pixels. ############################################### set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] ## FOR TESTING: if {0} { puts "proc 'draw_point_x1y1_radiusPx':" puts "x1: $x1 y1: $y1 x1Px: $x1Px y1Px: $y1Px" } ################################################################## ## Set the corner coords for the (small) circle. ################################################################## set topleftXpx [expr {$x1Px - $radiusPx}] set topleftYpx [expr {$y1Px - $radiusPx}] set botrightXpx [expr {$x1Px + $radiusPx}] set botrightYpx [expr {$y1Px + $radiusPx}] ################################################ ## Draw a circle at x1,y1. ################################################ .fRcanvas.can create oval \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ -fill $hexRGB -width $lineWidthPx -outline {} -tag $tagID ## If we want an outline around the circle, use something like this: # -width $lineWidthPx -outline $drawRGB } ## END OF proc 'draw_point_x1y1_radiusPx' ##+############################################################# ## proc 'Redraw' ## ## PURPOSE: Used to erase and redraw the lines,arcs,text,etc. ## on the canvas --- for the semi-circle and the ## inscribed triangle --- for example, ## after the user has resized the GUI window. ## ## This proc also is used to update the text in ## the labels of the '.fRinfo' frame. ## ## CALLED BY: The 'Additional GUI Initialization' section ## and a button1-release binding on the scale widget. ##+############################################################# proc Redraw {} { global drawRGB radsPERdeg SCALEangle ############################################################ ## Get current '.fRcanvas' dimensions --- in case the user ## has resized the window, and thus the '.fRcanvas' frame, ## which was packed with '-fill both -expand 1'. ############################################################ set curCanvasWidthPx [winfo width .fRcanvas] set curCanvasHeightPx [winfo height .fRcanvas] ## FOR TESTING: # puts "curCanvasWidthPx = $curCanvasWidthPx" # puts "curCanvasHeightPx = $curCanvasHeightPx" ################################################################## ## Set the corner pixel coords for the drawing. ## (NOT USED, maybe someday) ## We use 0,0 and curCanvasWidthPx,curCanvasHeightPx ## for the corners, without adjustment. ################################################################## # set topleftXpx $marginPx # set topleftYpx $marginPx # set botrightXpx [expr {$curCanvasSizePx - $marginPx}] # set botrightYpx [expr {$curCanvasSizePx - $marginPx}] ################################################################ ## Set the variables for converting world-coords to pixels. ################################################################ ## Recall input vars: ## mapping_for_wc2px(xORy,ULwcX,ULwcY,ULpxX,ULpxY,LRwcX,LRwcY,LRpxX,LRpxY) ## ## We map world-coord X-limits -1.2 and 1.2 ## TO pixel-coord X-limits 0 and canvasWidthPx. ## ## OR ## ## We map world-coord Y-limits 1.2 and -0.2 ## TO pixel-coord Y-limits 0 and canvasHeightPx. ## ## See code in 'mapping_for_wc2px' for details. ######################################################################## mapping_for_wc2px x -1.2 1.2 0 0 1.2 -0.2 $curCanvasWidthPx $curCanvasHeightPx ## Alternatively: # mapping_for_wc2px y -1.2 1.2 0 0 1.2 -0.2 $curCanvasWidthPx $curCanvasHeightPx ################################################################ ## Remove any previously drawn elements in this canvas, if any. ################################################################ ## If the window was not resized, the semi-circle would not ## have to be redrawn --- BUT checking whether the window is ## resized may take almost as much processing as simply redrawing ## the semi-circle. So we simply delete all geometry and redraw. ################################################################# catch {.fRcanvas.can delete all} ################################################################ ## Draw the semi-circle --- the top arc and the bottom line. ################################################################ ## Recall input vars: ## draw_arc_x1y1_degStart_degExtent ## (x1,y1,radius,degStart,degExtent,hexRGB,tagID) ## ## draw_line_x1y1x2y2 (x1,y1,x2,y2,hexRGB,tagID) ################################################################ draw_arc_x1y1_degStart_degExtent 0.0 0.0 1.0 0 180 $drawRGB TAGarc draw_line_x1y1x2y2 -1.0 0.0 1.0 0.0 $drawRGB TAGline ################################################################ ## Calculate the peak point of the triangle, given the ## current setting of the scale-widget variable 'SCALEangle'. ################################################################ set rads [expr {$SCALEangle * $radsPERdeg}] set xPeak [expr {cos($rads)}] set yPeak [expr {sin($rads)}] ################################################################ ## Draw the 2 legs for the 'peak' of the triangle. ################################################################ ## Recall input vars: ## draw_line_x1y1x2y2 (x1,y1,x2,y2,hexRGB,tagID) ################################################################ draw_line_x1y1x2y2 -1.0 0.0 $xPeak $yPeak $drawRGB TAGline draw_line_x1y1x2y2 1.0 0.0 $xPeak $yPeak $drawRGB TAGline ################################################################ ## Add a '90' text label just above the peak point of the ## triangle. ################################################################ ## Recall input vars: ## draw_text_x1y1_center (x1 y1 hexRGB tagID textSTRING) ################################################################ set x1Px [expr {0.85 * $xPeak}] set y1Px [expr {0.85 * $yPeak} - 0.05] draw_text_x1y1_center $x1Px $y1Px $drawRGB TAGtext "90 deg." ################################################################ ## Add a point (small circle) to the drawing --- at the origin ## (the center of the semi-circle's circle). ################################################################ ## Recall input vars: ## draw_point_x1y1_radiusPx {x1,y1,radiusPx,hexRGB,tagID} ################################################################ draw_point_x1y1_radiusPx 0.0 0.0 3 $drawRGB TAGpoint ################################################################ ## Add a '1' text label on each side of the center point --- ## to indicate that the radius of the semi-circle is one. ################################################################ ## Recall input vars: ## draw_text_x1y1_center (x1 y1 hexRGB tagID textSTRING) ################################################################ draw_text_x1y1_center 0.5 -0.1 $drawRGB TAGtext "1.0" draw_text_x1y1_center -0.5 -0.1 $drawRGB TAGtext "1.0" ################################################################ ## Put the circumference of the triangle in a label. ################################################################ set P1x -1.0 set P1y 0.0 set P2x 1.0 set P2y 0.0 set c [expr {$P2x - $P1x}] set a [expr {sqrt((($xPeak - $P1x) * ($xPeak - $P1x)) + (($yPeak - $P1y) * ($yPeak - $P1y)))}] set b [expr {sqrt((($xPeak - $P2x) * ($xPeak - $P2x)) + (($yPeak - $P2y) * ($yPeak - $P2y)))}] set circum [format "%8.3f" [expr {$a + $b + $c}]] .fRinfo.labelINFO1 configure -text "Triangle Circumference: $circum" ################################################################ ## Put the area of the triangle in a label. ################################################################ set area [format "%8.3f" [expr {0.5 * $a * $b}]] .fRinfo.labelINFO2 configure -text "Triangle Area: $area" } ## END OF proc 'Redraw' ##+############################################################# ## proc ReDraw_if_canvases_resized ## ## PURPOSE: To handle resizing the drawing when the window is ## resized --- IF the binding is implemented. ## ## The intent is to avoid too many redraws --- for ## almost every little resize of the window as its ## border(s) are dragged. ## ## CALLED BY: bind .fRcanvas.can ## at bottom of this script. ##+############################################################# ## NOT IMPLEMENTED. ## Code is included for possible future development. ##+############################################################# proc ReDraw_if_canvases_resized {} { global PREVcanvasesWidthPx PREVcanvasesHeightPx draw_wait0or1 ## FOR TESTING: (to dummy out this proc) # return if {$draw_wait0or1 == 1} {return} ## Set the wait indicator and delay doing the canvas resize ## check for about 300 milliseconds --- to allow time for the ## user to stop moving the window. After about 300 milliseconds, ## it is unlikely that the window is moving and thus causing ## multiple redraws. set draw_wait0or1 1 after 900 set CURcanvasesWidthPx [winfo width .fRcanvas] set CURcanvasesHeightPx [winfo height .fRcanvas] if { $CURcanvasesWidthPx != $PREVcanvasesWidthPx || \ $CURcanvasesHeightPx != $PREVcanvasesHeightPx} { ## The 'after' could be used to prevent too many ## redraws (and flickering and unnecessary processing) ## as the window is being moved. # after 200 Redraw set PREVcanvasesWidthPx $CURcanvasesWidthPx set PREVcanvasesHeightPx $CURcanvasesHeightPx set draw_wait0or1 0 } } ## END OF proc 'ReDraw_if_canvases_resized' #+##################################################################### ## proc 'set_draw_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 elements to be ## drawn on the canvas --- lines, arcs, text, etc. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLORdraw button ##+##################################################################### proc set_draw_color {} { global COLORDRAWr COLORDRAWg COLORDRAWb COLORDRAWhex drawRGB # global feDIR_tkguis ## FOR TESTING: # puts "COLORDRAWr: $COLORDRAWr" # puts "COLORDRAWg: $COLORDRAWb" # puts "COLORDRAWb: $COLORDRAWb" set TEMPrgb [ exec \ ./sho_colorvals_via_sliders3rgb.tk \ $COLORDRAWr $COLORDRAWg $COLORDRAWb] # $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 COLORDRAWhex "#$hexRGB" set COLORDRAWr $r255 set COLORDRAWg $g255 set COLORDRAWb $b255 ## Set color of the draw-color button. update_button_colors ## Set value of the 'drawRGB' global var and redraw. set drawRGB $COLORDRAWhex Redraw } ## END OF proc 'set_draw_color' ##+##################################################################### ## 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 (background) color of the canvas --- ## on which all the geometry is to be drawn. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLORbkgd button ##+##################################################################### proc set_background_color {} { global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex # global feDIR_tkguis ## FOR TESTING: # puts "COLORBKGDr: $COLORBKGDr" # puts "COLORBKGDg: $COLORBKGDb" # puts "COLORBKGDb: $COLORBKGDb" set TEMPrgb [ exec \ ./sho_colorvals_via_sliders3rgb.tk \ $COLORBKGDr $COLORBKGDg $COLORBKGDb] # $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \ ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLORBKGDhex "#$hexRGB" set COLORBKGDr $r255 set COLORBKGDg $g255 set COLORBKGDb $b255 ## Set color of background-color button. update_button_colors ## Set the color of the canvas. .fRcanvas.can config -bg $COLORBKGDhex } ## END OF proc 'set_background_color' ##+##################################################################### ## proc 'update_button_colors' ##+##################################################################### ## PURPOSE: ## This procedure is invoked to set the background color of each of ## 2 color buttons to its current color --- and sets foreground color, ## for text on the 2 buttons, to a suitable black or white color, ## so that the label text is readable. ## ## Arguments: global color vars ## ## CALLED BY: 2 colors procs: ## 'set_draw_color' ## 'set_background_color' ## and the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc update_button_colors {} { global COLORDRAWhex COLORBKGDhex \ COLORDRAWr COLORDRAWg COLORDRAWb \ COLORBKGDr COLORBKGDg COLORBKGDb # set colorBREAK 300 set colorBREAK 250 .fRbuttons.buttCOLORdraw configure -bg $COLORDRAWhex set sumCOLOR1 [expr {$COLORDRAWr + $COLORDRAWg + $COLORDRAWb}] if {$sumCOLOR1 > $colorBREAK} { .fRbuttons.buttCOLORdraw configure -fg "#000000" } else { .fRbuttons.buttCOLORdraw configure -fg "#f0f0f0" } .fRbuttons.buttCOLORbkgd configure -bg $COLORBKGDhex set sumCOLORBKGD [expr {$COLORBKGDr + $COLORBKGDg + $COLORBKGDb}] if {$sumCOLORBKGD > $colorBREAK} { .fRbuttons.buttCOLORbkgd configure -fg "#000000" } else { .fRbuttons.buttCOLORbkgd configure -fg "#f0f0f0" } } ## END OF proc 'update_button_colors' ##+######################################################################## ## PROC 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext } { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 wm geometry $toplevName +100+50 wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################## ## END of PROC definitions. ##+######################## ## Set HELPtext var. ##+######################## set HELPtext "\ \ \ ** HELP for this 'Thales Theorem Demo' App ** This Tk script is meant to demonstrate the Thales theorem that says: Any triangle inscribed in a semi-circle is a RIGHT triangle --- when one side of the triangle matches the flat, diametral side of the semi-circle. This GUI uses a Tk 'canvas' widget to show the semi-circle and a triangle inscribed within it. A 'scale' widget on the GUI allows the user to easily sweep through a family of triangles inscribed in the semi-circle. In this implementation, the diametral side of the semi-circle is displayed on the bottom side of the semi-circle, and the 'peak point' of the triangle(s) moves along the arc of the semi-circle --- while the other two vertices of the triangle stay fixed at the ends of the diametral side (line-segment) of the semi-circle. A goal of this GUI is to allow the canvas (and the geometry drawn within the canvas) to be easily resized if the GUI window is resized by the user. ************************** WINDOW RESIZE: We allow the user to resize the window rather than using a fixed window (and fixed drawing) size. If the user resizes the window, the 'Redraw' button can be used to force the drawing to be resized according to the new window size. **************************************************************************** USING THE SLIDER ON THE 'SCALE' WIDGET: You can click MB1 (mouse-button-1) on the 'slider' of the 'scale' widget and drag the slider. When the slider is released, a redraw of the figure is done. If you want FINE CONTROL of moving the slider, there are a couple of other options based on clicking in the trough on either side of the slider: 1) Click-and-release on either side of the slider, to change the figure by one increment of the scale per click-release. OR 2) Click-and-hold on either side of the slider. The slider moves until you release MB1. Then a redraw occurs. As the slider goes from 0 to 180, the 'peak point' of the triangle goes from the right side of the semi-circle to the left. (This behavior could easily be 'flipped'.) ****************************************************** ADDITIONAL ITEMS DISPLAYED: Various numeric properties of the triangle can be displayed as the 'peak point' is moved along the circumference of the semi-circle --- such as the the circumference of the triangle (sum of the lengths of the 3 sides) and the area of the triangle. **************************************************** SOME POSSIBLE ENHANCEMENTS: *More numeric properties* - Besides the circumference of the triangle (sum of the lengths of the 3 sides) and the area of the triangle, various other numeric properties of the triangle could be displayed as the 'peak point' is moved along the circumference of the semi-circle. For example: the other 2 angles of the triangle (in degrees or radians). *Describe Thales' proof* - Some extra lines and text could be drawn on the canvas to indicate Thales' proof of the fact (the theorem) --- that the 'peak angle' is always a 'right angle'. *Draw additional geometric elements* - Furthermore, various geometric properties/elements of the triangle could be displayed on the canvas. For example, the 'centroid' of the triangle could be drawn as a point inside the triangle, and the centroid would move (be erased and re-drawn) as the 'peak point' of the triangle is moved by the user. Perhaps it would offer more helpful information to draw additional line segments, like the radius from (0,0) to the 'peak point' --- or some perpendicular lines. ********************************************** CAPTURING THE GUI IMAGE: A screen/window capture utility (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG or GIF file, say. If necessary, an image editor (like 'mtpaint' on Linux) can be used to crop the window capture image. The image could also be down-sized --- say to make a smaller image suitable for use in a web page or an email. " ##+################################################################ ##+################################################################ ## Additional GUI INITIALIZATION: Mainly to ## - Draw an initial triangle and semicircle on the canvas. ##+################################################################ ##+################################################### ## Set the scale widget var for initial position (angle) ## for locating the peak of the triangle on the arc of ## the semi-circle. ##+################################################### set SCALEangle 90.0 ##+###################################################### ## Set a 'margin-percent' to use for placing the drawing ## within the 4 edges of the canvas. ## (NOT USED, yet) ##+###################################################### set marginPercent 10 # set marginPercent 5 ##+################################################ ## Set a width for lines (including arcs). ##+################################################ # set lineWidthPx 1 set lineWidthPx 2 ##+################################################# ## Set constants to use for angle conversions --- ## degrees to radians. ##+################################################ set pi [expr {4.0 * atan(1.0)}] set radsPERdeg [expr {$pi/180.0}] ##+############################################################ ## We need following command because the 'Redraw' proc ## (called below) does not (re)set the background/canvas color. ## Only the background-color button-proc sets the canvas color. ##+############################################################ .fRcanvas.can config -bg $COLORBKGDhex ## Nor does the 'Redraw' proc set the button colors. ## That is only done in the 2 set-color procs. update_button_colors ##+################################################# ## Draw the semi-circle and triangle, for the current ## canvas (window) size. ##+################################################# ## Need 'update' here to set the size of the canvas, ## because 'Redraw' uses 'winfo' to get ## the width and height of the canvas --- ## to support resizing the drawing if the window ## is resized. ##+################################################# update Redraw ##+#################################################### ## Set a resize binding on the canvas --- ## to redraw the semi-circle and triangle ## if the window is resized. ## ## DE-ACTIVATED, for now. ## (Code is here for future experimentation. ## When using the event, ## it is not easy to avoid extraneous redraws of ## the GUI as the window is being dragged/resized. ## And a better 'event' does not seem to be available.) ##+#################################################### if {0} { set draw_wait0or1 0 set PREVcanvasesWidthPx [winfo width .fRcanvas] set PREVcanvasesHeightPx [winfo height .fRcanvas] bind .fRcanvas "ReDraw_if_canvases_resized" } ====== <> ------ '''SOME POSSIBLE ENHANCEMENTS:''' '''More numeric properties''' - Besides the circumference of the triangle (sum of the lengths of the 3 sides) and the area of the triangle, various other numeric properties of the triangle could be displayed as the 'peak point' is moved along the circumference of the semi-circle. For example: the other 2 angles of the triangle (in degrees or radians). '''Describe Thales' proof''' - Some extra lines and text could be drawn on the canvas to indicate Thales' proof of the fact (the theorem) --- that the 'peak angle' is always a 'right angle'. A 'ProveIt' checkbutton could be added to the GUI. When the button is checked, lines would be drawn (some dashed) and text would be shown that explain the proof. When the button is un-checked, the lines and text would disappear. The proof-lines should move as the 'peak point' is moved. '''Draw additional geometric elements''' - Furthermore, various geometric properties/elements of the triangle could be displayed on the canvas. For example, the 'circumcenter' and/or 'centroid' points of the triangle could be drawn as points inside the triangle, and the points would move (be erased and re-drawn) as the 'peak point' of the triangle is moved by the user. A point like the 'intersection of lines from vertices to midpoints of sides' would lie on a line involved in the proof --- the radius from (0,0) to the 'peak point' --- and that point and that radius would move together as the 'peak point' is moved. '''Inner product calculation''' - The GUI shown above simply labels the 'peak angle' with '90 deg.' --- and it does appear that the angle is a right angle. However, looks can be deceiving. If the user accepts that the 'inner product' of 2 vectors is the product of their magnitudes and the cosine of the angle between them, then the GUI could show that the inner product of the two vectors * from (xPeak,yPeak) to (-1.0,0.0) * from (xPeak,yPeak) to (+1.0,0.0) is always zero, where (xPeak,yPeak) is the 'peak point' of the triangle --- on the arc between (-1.0,0.0) and (+1.0,0.0). Then we would be giving more rigorous proof that the 'peak angle' is a right angle. If I ever do an update of this script, I may add that feature. That would be taking us from about 600 B.C. to the Cartesian analytic geometry and vector analysis of the 19th and 20th centuries. ------ '''IN CONCLUSION:''' I would like to give my usual thanks to Ousterhout and the maintainers of the 'wish' interpreter for making all these mathematical and graphical utilities (that I have coded and that are on my 'to-do' list) possible.