Hello,
Thanks for your interest. I love Tcl/Tk and have done several interesting projects in it.
My involvement with the language has gone through phases: late 1990's, break, mid-to-late 2000's, break, and now back for a third time. Fortunately, the language has stayed pretty simple and mostly the same over the years so the returns were not as hectic as it could have been with other languages.
My first involvement was for solving Traveling Salesman Problem ( TSP ). My idea was to start with a convex hull and iteratively solve the remainder of the points on the map. This was a novel approach at the time (based on the survey I did then). There seems to be more researchers doing similar things these days. I first programmed it in C, got some promising results. In one instance, I even got a solution that was better than the one published by the above website as the optimal path. However, it was difficult to see where the algorithm was going astray. Enter Tcl/TK: After seeing a fellow student with Prof O's book, I quickly rewrote the app in Tcl. I could see the map visually, and watch the algorithm doing its thing step by step. Awesome! Unfortunately I had to leave it as oher things came up.
In my second return, I developed a data flow language for non-technical users so they could get their jobs done without needing IT too much, or almost none at all as it came to be :-) . This app had its success in its own limited way. There were boxes that each performed some function, and by connecting these boxes together, one could accomplish certain tasks. This was quite easy to use and powerful at the same time.
Currently, I am back and in the final stages of a more modern, more complete and more capable version of that data flow app. It is designed to be a data engineering platform for anyone who deals with data in one way or another. It has some quite advanced features: in its core functionality, as an app in its configuration and maintenance, and in its extra features. I can write about those later.
2023-01-11
Some technical info:
The data flow application above is an implementation of a directed acyclic graph or a DAG (DAG ). Basically, there are nodes and edges, represented as boxes and arrows. respectively. The edges are directional, meaning that an edge goes from one node to another, and it does not automatically imply that an edge with the reverse direction is also present. The graph is acyclic meaning that there are no cycles regardless of what sequence of edges you take to traverse through the graph.
An edge or arrow connects a node to another via its ports. A port is a construct that allows data to be transferred along the edges. As such, from a node's point of view, there are input ports where data comes in, and output ports where data leaves the node. In between the two ports, the node performs its action, whatever that may be.
A node implements a unit of functionality. By connecting them together in a network via various arrows one implements a higher-level business objective or a goal, or more familiarly a program. DAG implementations are usually domain specific.
While this is a mathematical concept in essence, this abstraction is quite useful in Computer Science and it has a number of applications.
The implementation above is driven purely by data, stored in a database. I originally developed it to process data in an Oracle database. It has of course grown to support other databases, including SQLite.
To be continued...
saito - 2022-12-25
Convex_Hull contains code to compute the convex hull of a given set of points
2023-01-22
A small fun project:
saito - 2023-02-17
Do you miss the little snipping utility from Windows 10? MS has removed it from Windows 11. But no worries...
saito - 2023-03-10
Create various shapes on canvas:
saito - 2023-05-10
Demo time:
saito - 2024-03-15
I have this little app that shows the time, plus it has a chronometer. I have used it for a while now and it was surprising to me how useful this little addition has made it. Options are not currently saved, but one could easily fill that gap.
So, I am sharing it here if anyone else finds it useful. chronotimer
Here is the code:
package require Tk ## schedule the first run after 40 [list chronoStart 990 0] proc getElapsedTime {seconds1 seconds2} { set eTime [expr {abs($seconds2 - $seconds1)}] set seconds [expr {$eTime % 60}] set eTime [expr {$eTime / 60}] set minutes [expr {$eTime % 60}] set eTime [expr {$eTime / 60}] set hours $eTime if {$hours == 0} { ## [format "%dmin %dsec" $minutes $seconds] set result [format "%d:%02d" $minutes $seconds] } else { ## [format "%dhr %dmin %dsec" $hours $minutes $seconds] set result [format "%d:%02d:%02d" $hours $minutes $seconds] } return $result } proc updateTimer {currWin elapseWin} { global opts if {$opts(inMotion)} { after $opts(refreshMS) [list updateTimer $currWin $elapseWin] return } set currTime [clock seconds] if {$opts(showMilli)} { set milliSecs ".[string range [clock milli] end-2 end]" } else { set milliSecs "" } if {$opts(use24)} { set now [clock format $currTime -format {%H:%M:%S}] } else { set now [clock format $currTime -format {%l:%M:%S}] set now [string trim $now] } $currWin configure -text " ${now}${milliSecs} " $elapseWin configure -text [getElapsedTime $currTime $opts(startSecs)] after $opts(refreshMS) [list updateTimer $currWin $elapseWin] } proc chronoStart {{refreshMS 50} {showMilliSecs 1}} { global opts ## colors set opts(alpha) 100 set opts(bgColor) #ffffb9 set opts(fgColor1) #ff8000 set opts(fgColor2) #0080ff ## fonts ## {Consolas 24 bold} ## {Consolas 36 bold} ## {Ravie 36 bold} ## {{Comic Sans MS} 36 bold} ## {Ravie 24 bold} ## {{Comic Sans MS} 24 bold} set opts(fontCurr) {Consolas 39 bold} set opts(fontElapsed) {Consolas 24 bold} ## controls set opts(use24) 1 set opts(refreshMS) $refreshMS set opts(showMilli) $showMilliSecs set opts(startSecs) [clock seconds] set top . wm title $top "Chrono-Timer" wm minsize $top 360 130 wm resizable $top 0 0 set frWin .show set optFr .opts $top config -bg $opts(bgColor) frame $frWin -bd 2 -bg $opts(bgColor) frame $optFr -bd 2 wm attributes $top -alpha [expr {$opts(alpha) / 100.}] label $frWin.lCurrTime -font $opts(fontCurr) \ -background $opts(bgColor) \ -foreground $opts(fgColor1) label $frWin.lElapsed -font $opts(fontElapsed) \ -background $opts(bgColor) \ -foreground $opts(fgColor2) grid $frWin.lCurrTime -row 1 -column 1 -sticky news -columnspan 3 grid $frWin.lElapsed -row 2 -column 3 -sticky news grid rowconfigure $frWin 1 -weight 1 grid rowconfigure $frWin 2 -weight 1 grid columnconfigure $frWin 1 -weight 1 grid columnconfigure $frWin 2 -weight 1 grid columnconfigure $frWin 3 -weight 1 grid $frWin -row 1 -column 1 -sticky news grid $optFr -row 2 -column 1 -sticky news grid rowconfigure $top 1 -weight 0 grid rowconfigure $top 2 -weight 1 grid columnconfigure $top 1 -weight 1 grid forget $optFr label $optFr.l1 -text "Display" -anchor e radiobutton $optFr.showms -text "Show milli" \ -anchor w \ -width 10 \ -highlightthickness 0 \ -value 1 \ -variable opts(showMilli) radiobutton $optFr.hidems -text "Hide milli" \ -anchor w \ -width 10 \ -highlightthickness 0 \ -value 0 \ -variable opts(showMilli) checkbutton $optFr.use24 -text "Use Military Time" \ -anchor w \ -width 10 \ -justify right \ -variable opts(use24) label $optFr.l2 -text "Time" -anchor e button $optFr.b2 -text "Change Color" \ -width 12 -command [list chronoChangeColor fgColor1] label $optFr.l3 -text "Chrono" -anchor e button $optFr.b3 -text "Change Color" \ -width 12 -command [list chronoChangeColor fgColor2] label $optFr.l4 -text "Background" -anchor e button $optFr.b4 -text "Change Color" \ -width 12 -command [list chronoChangeColor bgColor] label $optFr.l5 -text "Refresh (msecs)" -anchor e entry $optFr.e5 -width 5 -textvar opts(refreshMS2) label $optFr.l6 -text "Alpha %" -anchor se scale $optFr.alpha -from 10 -to 100 -showvalue 1 -bd 2 \ -sliderrelief ridge -resolution 5 -orient horizontal \ -variable opts(alpha) set bFr $optFr.bFr frame $bFr -bd 0 -relief flat button $bFr.save -text "Save" \ -state disabled \ -width 6 -command [list chronoSaveOpts $top] button $bFr.reset -text "Reset" \ -width 6 -command [list chronoResetTimer $top] button $bFr.hide -text "Hide" \ -width 6 -command [list chronoToggleDisplay $top] button $bFr.quit -text "Quit" \ -fg red \ -width 6 -command [list chronoQuit $top] grid $bFr.save -row 1 -column 1 -sticky news -padx 4p grid $bFr.reset -row 1 -column 2 -sticky news -padx 4p grid $bFr.hide -row 1 -column 3 -sticky news -padx 4p grid $bFr.quit -row 1 -column 4 -sticky news -padx 4p grid rowconfigure $bFr 1 -weight 0 grid columnconfigure $bFr 1 -weight 1 grid columnconfigure $bFr 2 -weight 1 grid columnconfigure $bFr 3 -weight 1 grid columnconfigure $bFr 4 -weight 1 grid $optFr.l1 -row 1 -column 1 -sticky news -padx 2p grid $optFr.showms -row 1 -column 2 -sticky news -padx 2p grid $optFr.hidems -row 2 -column 2 -sticky news -padx 2p grid $optFr.use24 -row 3 -column 2 -sticky news -padx 2p -pady 2p grid $optFr.l2 -row 4 -column 1 -sticky news -pady 4 -padx 2p grid $optFr.b2 -row 4 -column 2 -sticky news -pady 4 -padx 2p grid $optFr.l3 -row 5 -column 1 -sticky news -pady 4 -padx 2p grid $optFr.b3 -row 5 -column 2 -sticky news -pady 4 -padx 2p grid $optFr.l4 -row 6 -column 1 -sticky news -pady 4 -padx 2p grid $optFr.b4 -row 6 -column 2 -sticky news -pady 4 -padx 2p grid $optFr.l5 -row 7 -column 1 -sticky news -pady 4 -padx 2p grid $optFr.e5 -row 7 -column 2 -sticky news -pady 4 -padx 2p grid $optFr.l6 -row 8 -column 1 -sticky news -pady 2 -padx 2p grid $optFr.alpha -row 8 -column 2 -sticky news -pady 2 -padx 2p grid $optFr.bFr -row 9 -column 1 -sticky ews -pady {10p 4p} -columnspan 3 grid rowconfigure $optFr 1 -weight 0 grid rowconfigure $optFr 2 -weight 0 grid rowconfigure $optFr 3 -weight 0 grid rowconfigure $optFr 4 -weight 0 grid rowconfigure $optFr 5 -weight 0 grid rowconfigure $optFr 6 -weight 0 grid rowconfigure $optFr 7 -weight 0 grid rowconfigure $optFr 8 -weight 1 grid columnconfigure $optFr 1 -weight 1 grid columnconfigure $optFr 2 -weight 1 grid columnconfigure $optFr 3 -weight 1 bind $frWin <ButtonRelease-1> [list chronoToggleDisplay $top] bind $frWin.lCurrTime <ButtonRelease-1> [list chronoToggleDisplay $top] bind $frWin.lElapsed <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr.l1 <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr.l2 <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr.l3 <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr.l4 <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr.l5 <ButtonRelease-1> [list chronoToggleDisplay $top] bind $optFr.l6 <ButtonRelease-1> [list chronoToggleDisplay $top] bind $top <Button-1> [list chronoStartMotion %W %x %y] bind $frWin <B1-Motion> [list chronoMoveTimerWindow %W %x %y] bind $frWin.lCurrTime <B1-Motion> [list chronoMoveTimerWindow %W %x %y] bind $frWin.lElapsed <B1-Motion> [list chronoMoveTimerWindow %W %x %y] set opts(inMotion) 0 set opts(cfgOpen) 0 set opts(timeFr) $frWin set opts(optFr) $optFr set opts(timeWin) $frWin.lCurrTime set opts(chronWin) $frWin.lElapsed set opts(refreshMS2) $opts(refreshMS) ## start updateTimer $frWin.lCurrTime $frWin.lElapsed } proc chronoSaveOpts {top} {} proc chronoQuit {top} {exit} proc getWindowWidth {win} { lassign [split [winfo geometry $win] "x+"] origW origH origX origY return $origW } proc getWindowHeight {win} { lassign [split [winfo geometry $win] "x+"] origW origH origX origY return $origH } proc chronoStartMotion {win x y} { global opts set opts(xOffSet) $x set opts(yOffSet) $y } proc chronoMoveTimerWindow {win x y} { global opts set opts(inMotion) 1 set topWin [winfo toplevel $win] lassign [split [winfo geometry $topWin] "+"] origDims origX origY set newX [expr {$origX + $x}] set newY [expr {$origY + $y}] set newX [expr {$newX - $opts(xOffSet)}] set newY [expr {$newY - $opts(yOffSet)}] wm geometry $topWin "${origDims}+${newX}+${newY}" } proc chronoResetTimer {top} { global opts set opts(startSecs) [clock seconds] chronoToggleDisplay $top } proc chronoToggleDisplay {top} { global opts if {$opts(inMotion)} { set opts(inMotion) 0 return } set opts(refreshMS2) [string trim $opts(refreshMS2)] if {[string is integer -strict $opts(refreshMS2)]} { set opts(refreshMS) $opts(refreshMS2) } set opts(refreshMS2) $opts(refreshMS) if {$opts(cfgOpen)} { set opts(cfgOpen) 0 grid forget $opts(optFr) ## resize back to timer window wm resizable $top 0 0 wm minsize $top [getWindowWidth $opts(timeFr)] \ [getWindowHeight $opts(timeFr)] wm minsize $top 360 130 wm maxsize $top [getWindowWidth $opts(timeFr)] \ [getWindowHeight $opts(timeFr)] } else { set opts(cfgOpen) 1 grid $opts(optFr) -row 2 -column 1 -sticky news ## make sure config opts are visible wm resizable $top 1 1 wm minsize $top 360 500 wm maxsize $top [winfo screenwidth .] [winfo screenheight .] } wm attributes $top -alpha [expr {$opts(alpha) / 100.}] } proc chronoChangeColor {opt} { global opts set new [tk_chooseColor -initialcolor $opts($opt) -title "Choose Color" ] if {$new ne ""} { set opts($opt) $new } switch $opt { fgColor1 { $opts(timeWin) config -background $opts(bgColor) \ -foreground $opts(fgColor1) } fgColor2 { $opts(chronWin) config -background $opts(bgColor) \ -foreground $opts(fgColor2) } bgColor { $opts(timeFr) config -bg $opts(bgColor) $opts(timeWin) config -bg $opts(bgColor) $opts(chronWin) config -bg $opts(bgColor) } } }