Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Can this code to choose a tea be improved

54 views
Skip to first unread message

Cecil Westerhof

unread,
Jun 25, 2018, 9:44:05 AM6/25/18
to
I wrote a command-line tcl application to select a tea to brew. One
proc is the following:
proc chooseTea {teaList} {
set nrOfTeas [llength ${teaList}]
for {set i 0} {${i} < ${nrOfTeas}} {incr i} {
set tea [lindex ${teaList} ${i}]
puts [format "%d: %-30s %-10s %2s" \
[expr ${i} + 1] \
[dict get ${tea} Tea] \
[dict get ${tea} LastUsed] \
[dict get ${tea} Location]]
}
while {true} {
set choice [getInput "Which tea: "]
if {(${choice} == "#q") || (${choice} == "#r")} {
return ${choice}
} elseif {(${choice} >= 1) && (${choice} <= ${nrOfTeas})} {
incr choice -1
set entry [lindex ${teaList} ${choice}]
set choosen {}
lappend choosen \
[dict get ${entry} Tea] \
[dict get ${entry} Location]
return ${choosen}
}
puts "Input incorrect."
}
}

It gets a list with teas. Displays them and let you select one. With
#q or #r you can exit the selection. Otherwise you have to enter a
correct value and the corresponding tea and location is returned in a
list.

Can this be improved?

--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof

Rich

unread,
Jun 25, 2018, 11:14:33 AM6/25/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> I wrote a command-line tcl application to select a tea to brew.
>
> It gets a list with teas. Displays them and let you select one. With
> #q or #r you can exit the selection. Otherwise you have to enter a
> correct value and the corresponding tea and location is returned in a
> list.
>
> Can this be improved?

One small change (not sure it could be classed as an "improvement" per
se.) could be changing this (which looks very C like) to be more Tcl
like:

> for {set i 0} {${i} < ${nrOfTeas}} {incr i} {
> set tea [lindex ${teaList} ${i}]
> puts [format "%d: %-30s %-10s %2s" \
> [expr ${i} + 1] \
> [dict get ${tea} Tea] \
> [dict get ${tea} LastUsed] \
> [dict get ${tea} Location]]
> }

set i 0
foreach tea $teaList {
puts [format "%d: %-30s %-10s %2s" \
[incr i] \
[dict get ${tea} Tea] \
[dict get ${tea} LastUsed] \
[dict get ${tea} Location]]
}


Provided you don't have variable name conflicts from keys in the dicts,
you can further shorten by using "dict with" to expand the dict (dict
with operates /very/ similarly to the "with" statement from Pascal, for
accessing fields of records without needing to attach the record
prefix, only for dicts in Tcl):

set i 0
foreach tea $teaList {
dict with $tea {
puts [format "%d: %-30s %-10s %2s" \
[incr i] $Tea $LastUsed $Location]
}
}

Note that if you /do/ have variable name conflicts with the dict keys,
you can substitute "dict update" for "dict with" and specify which keys
should be expanded, and into what variable names their contents should
be placed. More verbose than the dict with case, but you have more
control on exactly what happens.

jda...@gmail.com

unread,
Jun 25, 2018, 2:48:05 PM6/25/18
to
since this is a user interface, you might consider using Tk

pop up a window to select a tea, or cancel (I'm assuming the list is not too long)

proc chooseTea {teas} {
destroy .tea
toplevel .tea
set lb [listbox .tea.lb -height [llength $teas] -width 50 \
-selectmode single]
pack $lb -side top
bind $lb <<ListboxSelect>> [list makeTea $teas]
foreach tea $teas {
dict with tea {
$lb insert end [format {%-30s %-10s %2s} $Tea $LastUsed $Location]
}
}
pack [button .tea.cancel -text Cancel -command [list destroy .tea]] \
-side top
}


if a tea is selected, destroy selection window and use the selected tea

proc makeTea {teas} {
set tea [lindex $teas [.tea.lb curselection]]
destroy .tea
dict with tea {
puts "brew $Tea, from $Location"
}
}


Checking for proper inputs is handled by the interface. However code is added for managing windows, and makeTea must handle the selected tea of returning it from chooseTea, the normal event driven programming stuff.

Dave

Cecil Westerhof

unread,
Jun 25, 2018, 3:14:05 PM6/25/18
to
Good idea.


> Provided you don't have variable name conflicts from keys in the dicts,
> you can further shorten by using "dict with" to expand the dict (dict
> with operates /very/ similarly to the "with" statement from Pascal, for
> accessing fields of records without needing to attach the record
> prefix, only for dicts in Tcl):
>
> set i 0
> foreach tea $teaList {
> dict with $tea {
> puts [format "%d: %-30s %-10s %2s" \
> [incr i] $Tea $LastUsed $Location]
> }
> }

That is a nice one, but you need only the name not the $. It becomes:
set i 0
foreach tea ${teaList} {
dict with tea {
puts [format "%d: %-30s %-10s %2s" \
[incr i] ${Tea} ${LastUsed} ${Location}]

Cecil Westerhof

unread,
Jun 26, 2018, 2:44:04 AM6/26/18
to
jda...@gmail.com writes:

> since this is a user interface, you might consider using Tk

That is something I also want to implement: it should work from the
command-line and with a GUI.

Cecil Westerhof

unread,
Jun 26, 2018, 3:14:04 AM6/26/18
to
I do not have experience with GUI stuff. I tried this, but nothings
happens. I changed it a little:
dict with tea {
puts "${Tea} ${LastUsed} ${Location}"
$lb insert end [format {%-30s %-10s %2s} $Tea $LastUsed $Location]
}

And I see:
Duizendblad 2018-06-15 9
Moul Ataï 2018-06-15 12
Herfst 2018-06-15 A2
Kruizemunt 2018-06-17 8
Jasmijn 2018-06-18 4
Sinaasappel 2018-06-18 A3
Brandnetel 2018-06-19 1

But I do not get to see the window. What do I need to do?

I did 'package require Tk'.

Arjen Markus

unread,
Jun 26, 2018, 3:45:44 AM6/26/18
to
On Tuesday, June 26, 2018 at 9:14:04 AM UTC+2, Cecil Westerhof wrote:
Could you show as the entire script? (And perhaps add "Sterremunt" in the process?)

Regards,

Arjen

Cecil Westerhof

unread,
Jun 26, 2018, 4:59:05 AM6/26/18
to
The script is more as 250 lines and uses a database and my own
library, but I try to cook something up.

Cecil Westerhof

unread,
Jun 26, 2018, 5:14:04 AM6/26/18
to
Below an example.

When executing and entering 7 I get:
Which action: 7
Call chooseTeaTk
Entering chooseTeaTk
Duizendblad 2018-06-15 9
Moul Ataï 2018-06-15 12
Herfst 2018-06-15 A2
Leaving chooseTeaTk
Returned

Which action:

So the proc makeTea is never called.


#!/usr/bin/env tclsh

package require Tk


proc chooseTeaTk {teaList} {
puts "Entering chooseTeaTk"
destroy .tea
toplevel .tea
set lb [listbox .tea.lb -height [llength $teaList] -width 50 \
-selectmode single]
pack $lb -side top
bind $lb <<ListboxSelect>> [list makeTea $teaList]
foreach tea $teaList {
dict with tea {
puts [format {%-30s %-10s %2s} $Tea $LastUsed $Location]
$lb insert end [format {%-30s %-10s %2s} $Tea $LastUsed $Location]
}
}
pack [button .tea.cancel -text Cancel -command [list destroy .tea]] \
-side top
puts "Leaving chooseTeaTk"
}

# Getting input from the terminal while displaying the prompt.
proc getInput {prompt} {
if {(${prompt} ne "") && ([string index ${prompt} end] ne " ")} {
set prompt "${prompt}: "
}
puts -nonewline ${prompt}
flush stdout
gets stdin
}

proc makeTea {teas} {
puts "Entering makeTea"
set tea [lindex $teas [.tea.lb curselection]]
destroy .tea
dict with tea {
puts "brew $Tea, from $Location"
}
puts "Entering makeTea"
}


set teaList []
lappend teaList [dict create \
Tea Duizendblad \
LastUsed 2018-06-15 \
Location 9 \
]
lappend teaList [dict create \
Tea "Moul Ataï" \
LastUsed 2018-06-15 \
Location 12 \
]
lappend teaList [dict create \
Tea Herfst \
LastUsed 2018-06-15 \
Location A2 \
]
while {true} {
set choice [getInput "Which action: "]
if {${choice} == "#q"} {
exit
}
switch ${choice} {
7 {
puts "Call chooseTeaTk"
chooseTeaTk ${teaList}
puts Returned
}
default {
puts "Wrong input."
}
}
puts ""

Ralf Fassel

unread,
Jun 26, 2018, 7:42:29 AM6/26/18
to
* Cecil Westerhof <Ce...@decebal.nl>
| So the proc makeTea is never called.
--<snip-snip>--
| # Getting input from the terminal while displaying the prompt.
| proc getInput {prompt} {
| if {(${prompt} ne "") && ([string index ${prompt} end] ne " ")} {
| set prompt "${prompt}: "
| }
| puts -nonewline ${prompt}
| flush stdout
| gets stdin
| }
--<snip-snip>--
| while {true} {
| set choice [getInput "Which action: "]
...

You are always blocking inside 'gets' in getInput, so the GUI never gets
the chance to do anything (eg. showing the listbox, or calling the
callback on selection).

Either change the stdin code to fileevents, or change getInput to also
use the GUI (e.g. an entry). In any case you need to enter the event
loop in order for the GUI code to work.

HTH
R'

Arjen Markus

unread,
Jun 26, 2018, 7:57:28 AM6/26/18
to
On Tuesday, June 26, 2018 at 1:42:29 PM UTC+2, Ralf Fassel wrote:
> * Cecil Westerhof
The Wiki has plenty of examples for how to do this. A more typical use would be to simply display the possible choices in a listbox and allow the user to interact with that listbox.

Here is a very very basic GUI:

# choosetea.tcl --
# Select a tea - very basic!
#
proc teaSelected {listbox} {
puts "Tea: [$listbox get [$listbox curselect]]"
}

#
# set up GUI
#
pack [listbox .lb -listvariable teaList]

set teaList {Duizendblad "Moul Atai" Herfst}

bind .lb <<ListboxSelect>> [list teaSelected .lb]

catch {console show}




Regards,

Arjen

Cecil Westerhof

unread,
Jun 26, 2018, 8:59:05 AM6/26/18
to
That looks interesting. I have to look into that. The whole GUI thing
is new to me. (The programming part.) But in most cases a GUI would be
better as a command-line interface. So I have a new pet project. ;-)

Rich

unread,
Jun 26, 2018, 11:22:08 AM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> jda...@gmail.com writes:
>
>> since this is a user interface, you might consider using Tk
>
> That is something I also want to implement: it should work from the
> command-line and with a GUI.

You could make it 'dual purpose' by detecting presence of a DISPLAY
env. variable.

At least under Linux that will generally let you know if a GUI is
available.

Alternately, you could 'catch' a package require of Tk, then act
differently dependent upon failure/success in loading Tk.

Rich

unread,
Jun 26, 2018, 11:24:03 AM6/26/18
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
>> Checking for proper inputs is handled by the interface. However code
>> is added for managing windows, and makeTea must handle the selected
>> tea of returning it from chooseTea, the normal event driven
>> programming stuff.
>
> I do not have experience with GUI stuff. I tried this, but nothings
> happens. I changed it a little:
> dict with tea {
> puts "${Tea} ${LastUsed} ${Location}"
> $lb insert end [format {%-30s %-10s %2s} $Tea $LastUsed $Location]
> }
>
> And I see:
> Duizendblad 2018-06-15 9
> Moul Ataï 2018-06-15 12
> Herfst 2018-06-15 A2
> Kruizemunt 2018-06-17 8
> Jasmijn 2018-06-18 4
> Sinaasappel 2018-06-18 A3
> Brandnetel 2018-06-19 1
>
> But I do not get to see the window. What do I need to do?
>
> I did 'package require Tk'.
>

Did you ever enter the event loop? Unless your script completed (or
you did an explicit vwait on a variable) you never entered the event
loop. And almost everything in Tk happens via the event loop.

If you ended up at your "gets" from the CLI version, you never actually
entered the event loop.
0 new messages