tags:

views:

934

answers:

6
A: 

Here's a little tcl snippet - run from tclsh or wish

[nigel@rose ~]$ wish
% set foo /dev/ttys0
/dev/ttys0
% puts $foo
/dev/ttys0
% puts "$foo"
/dev/ttys0
% puts [$foo]
invalid command name "/dev/ttys0"
% puts ($foo)
(/dev/ttys0)
% puts {$foo}
$foo
%

Quoting in Tcl:

  • "" (double quotes): Evaluate substitutions ($variable)

  • {} {Squiggly brackets): Treat the entire string as a literal with no substitution

  • [] (Square brackets): Execute the string as a command with substitution

As an alternative you could pop up the diagnostic in a dialog box:

% set mb [tk_messageBox -message "Port is $foo" -type ok -icon info]
ok
%
ConcernedOfTunbridgeWells
It has to work in a script, not just from the Tcl command line. I think that's why I'm having trouble finding the right syntax.
kajaco
kajaco, your comment doesn't seem to address the real problem at all.
Bryan Oakley
A: 

At your "# Just here" comment, try adding

puts $::gretagNum

:: signifies a global variable, and the -variable option to widgets are always global.

Hugge
or adding global gretagNumshould help
Arkadiy
A: 

I'm not sure what you want to do, put if you end put printing the variable name and not the variable content, use the set command as function:

set content "hello"

set blah content

if you do:

puts $blah

.. you get the content of the blah variable, which is content.

To get the content of the variable content via blah, use the following:

puts [set $blah]
Roalt
+2  A: 

You have not assigned any variable to the colorimetric checkbutton. Add -variable colorimetric to the checkbutton, and then in finish you can use: puts $::colorimetric

Also, set ::colorimetric first to select your default value. That is easier than trying to mess with the state of the widget.

I see that the values colorimetric can have are "" and "-c" so I assume you will use that value in the exec line. Beware that [exec something yada $::colorimetric something] will probably not work then. You'll probably need {*}$::colorimetric in the exec line to make the argument disappear if it is empty.

Hugge
A: 

Just remember this rule and you will always get it right:

$foo is shorthand for [set foo]

Try rewriting your code with [set foo] and it will work.

In particular,

$$foo  ;# not what you think

is replaced by

[set [set foo]]   ;# does what you think
Mark Harrison
while you are technically correct, this does nothing to answer the actual question.
Bryan Oakley
+1  A: 

Variable Basics

As others have pointed out, the confusing to $ or not to $ notation can be simplified by the following rule.

var is a reference to the variable itself, not its value

$var yields the value held in the variable

It can become a little more confusing when you start to think of everything in Tcl as a string (it's really not, but close enough), so you can store the name of one variable in another and restore its value by reference.

% set foo "hi"
hi
% set bar "foo"
foo
% set $foo
can't read "hi": no such variable
% set $bar
hi

Here the notation set $foo is evaluated in step - first $foo yields its value hi and then the set expression (when run with no third argument) attempts to return the value held in the variable hi. This fails. The notation set $bar takes the same steps but this time set can operate on the value of bar, which is foo, and thus returns the value of foo which is hi. (link to "set" API)

Initialization

One problem you have in this script is initialization. In Tcl variables don't exist until they're assigned a value. That's clearly why trying to set $foo above didn't work, because there was no variable hi.

At the top of your script you attempt to declare a variable with,

global colorimetric

which doesn't work, because you are already operating in global scope. Global "has no effect unless executed in the context of a proc body." (link to "global" API) You actually have to use a set command to initialize the variable. This is why none of your attempts to print colorimetric in proc finish worked.

Scope

The other problem you have in this script is scope, particularly with mixing global and procedural/local scope. You're correct that, had you initialized colorimetric correctly then the code,

puts $::colorimetric ;# print the value of the global variable colorimetric

would have worked. Another way to achieve this is with,

global colorimetric ;# reference a global variable into the local scope
puts $colorimetric  ;# print the value of colorimetric in the local scope

My Solution

I'd like to present my solution. I admit that I've moved a lot of code around, and I will go into a short explanation of what changes I implemented to make it more concise.

#!/usr/bin/env wish

# --- default configuration --- #
array set CONF {
    colorimetric "-c"
    spectral     ""
    cfilename    "/path/to/defaultCI.txt"
    sfilename    ""
    x            0
    y            0
    gretagnum    "/dev/ttyS0"
    filter       "-d65"
    baud         "B9600"
}

# --- build the interface --- #
wm title . "Gretag"
ttk::frame .f -borderwidth 5 -relief sunken -padding "5 10"
grid columnconfigure . 0 -weight 1
grid rowconfigure    . 0 -weight 1
grid .f


ttk::label .f.dataLabel -text "Data Type: "
foreach {dtname dttag dtfile} {
    colorimetric "-c" cfilename
    spectral     "-s" sfilename
} {
    lappend mygrid [
     ttk::checkbutton .f.$dtname -text [string totitle $dtname] \
     -variable CONF($dtname) -onvalue $dttag -offvalue "" \
     -command [list getFilename $dtname $dttag $dtfile ]
    ]
}
grid .f.dataLabel {*}$mygrid -sticky w ; set mygrid { }


ttk::label .f.gretagNumLabel -text "Gretag #: "
for {set tty 0} {$tty < 5} {incr tty} {
    lappend mygrid [
     ttk::radiobutton .f.gretagRadio$tty -text [expr $tty + 1] \
     -variable CONF(gretagnum) -value "/dev/ttyS$tty" 
    ]
}
grid .f.gretagNumLabel {*}$mygrid -sticky w ; set mygrid { }


ttk::label .f.sampleSize -text "Sample Size: "
ttk::label .f.samplex -text "X"
ttk::label .f.sampley -text "Y"
ttk::entry .f.x -textvariable CONF(x) -width 5 
ttk::entry .f.y -textvariable CONF(x) -width 5 
grid .f.sampleSize .f.samplex .f.x .f.sampley .f.y


ttk::label .f.filterLabel -text "Filter Type: "
foreach {ftname ftval} {
    D50        "-d50"
    D65        "-d65"
    Unfiltered "-U"
    Polarized  "-P"
} {
    lappend mygrid [
     ttk::radiobutton .f.filterRadio$ftname -text $ftname \
     -variable CONF(filter) -value $ftval
    ]
}
grid .f.filterLabel {*}$mygrid -sticky w ; set mygrid { }


ttk::label .f.baudLabel -text "Baud Rate: "
foreach {baud} {
    4800 9600 19200 38400 57600
} {
    lappend mygrid [
     ttk::radiobutton .f.baudRadio$baud -text $baud \
     -variable CONF(baud) -value "B$baud"
    ]
}
grid .f.baudLabel {*}$mygrid -sticky w ; set mygrid { }


ttk::button .f.submitBtn -text "Submit" -command submit
grid .f.submitBtn -columnspan 6 -sticky we

foreach w [winfo children .f] {
    grid configure $w -padx 5 -pady 5
}
focus .f.colorimetric
bind . <Return> submit

# --- callbacks --- #
proc getFilename {type tag file} {
    global CONF

    if {$CONF($type) eq $tag} {
     set CONF($file) [tk_getOpenFile]
     if {$CONF($file) eq ""} { .f.$type invoke }
    } else {
     set CONF($file) ""
    }
}

proc submit { } {
    global CONF

    exec ./gretag $CONF(colorimetric) $CONF(cfilename) \
     $CONF(spectral) $CONF(sfilename) $CONF(gretagnum) \
     $CONF(x) $CONF(y) $CONF(filter) $CONF(baud)
}

Discussion of Changes

1. The first changes I made were to use the -text options on the ttk::checkbutton and ttk::radiobutton. Granted, using an extra label for these allows you to place the text before the button, but doing so is non-standard and requires more code.

ttk::label .f.colorimetricLabel -text "Colorimetric"
ttk::checkbutton .f.colorimetric -onvalue "-c" -offvalue "" -command getFilename1

becomes

ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1


2. Next I used the similarities between these two checkbuttons to abstract the creation into a foreach. (I do this all the time in my Tcl code for work.) This generates much easier code to read and allows you to add/remove/swap names and tags for the widgets. It results in slightly more but much more versitile code.

ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1
ttk::checkbutton .f.colorimetric -text "Spectral" -onvalue "-s" -offvalue "" -command getFilename2

becomes

foreach {dtname dttag dtcommand} {
    colorimetric "-c" getFilename1
    spectral     "-s" getFilename2
} {
        ttk::checkbutton .f.$dtname -text [string totitle $dtname] -onvalue $dttag -offvalue "" -command $dtcommand
}


3. The next change was to merge your getFilename1 and getFilename2 into a single getFilename procedure. We can pass arguments into this function to determine who is calling it. I use the list command to generate the call for this new function. (link to "list" API)

I also started to combine your grid commands into the widget code itself. Here mygrid keeps a list of what needs to be gridded per line in the GUI and then is evaluated at the end of each section to propagate the GUI on the fly. (link to "grid" API)

The previous code gets its final revision and becomes,

foreach {dtname dttag dtfile} {
    colorimetric "-c" cfilename
    spectral     "-s" sfilename
} {
    lappend mygrid [
     ttk::checkbutton .f.$dtname -text [string totitle $dtname] -variable CONF($dtname) -onvalue $dttag -offvalue "" -command [list getFilename $dtname $dttag $dtfile ]
    ]
}

Then the grid command can be evaluated and the mygrid variable cleared after every use!


4. If you've been paying attention I also added a -variable option to your ttk::checkbutton and at this point started storing the button state in a global variable CONF. This is a big change.

Tk loves to pollute your global namespace and it's good practice to fight back. I usually put all of my configuration state in a global array, and set that right up top of the code so that anyone can come in and change the default state of my code, without digging into it or doing search/replace calls or anything like that. Just good practice, so wherever you had a variable I moved it into CONF and moved CONF up top.

array set CONF {
    colorimetric "-c"
    spectral     ""
    cfilename    "/path/to/defaultCI.txt"
    sfilename    ""
    x            0
    y            0
    gretagnum    "/dev/ttyS0"
    filter       "-d65"
    baud         "B9600"
}


5. Next I propagated these changes throughout your code. Almost all of the widget creation was susceptible to these revisions. With respect to the widget creation this sometimes made independent code sections larger. But it also allowed me to remove your entire grid section, merging this code up into the widget code as I've discussed, greatly decreasing the size and clutter of your code at the added expense of complexity.


6. The final changes I made were to your function code. You have a couple of minor bugs in your getFilename1 and getFilename2 code. The first bug was that you want to call tk_getOpenFile because I gather you are only grabbing an existing file name to pass it to gretag. (link to 'tk_getOpenFile' API) If you use tk_getOpenFile the dialog will make sure the file exists.

The second bug in getFilename1 is that the dialog has a Cancel button, and if the user clicks this cancel button the dialog returns an empty string. In this case you have to un-check the ttk::checkbutton and you have to unset the CONF(colorimetric) variable. Or more correctly you have to set CONF(colorimetric) to the button's -offvalue. You can do both of these at once by sending a click event to the current button.

proc getFilename1 {} {
set filename1 [tk_getSaveFile]
}

becomes

proc getFilename {type tag file} {
    global CONF

    if {$CONF($type) eq $tag} {
     set CONF($file) [tk_getOpenFile]
     if {$CONF($file) eq ""} { .f.$type invoke }
    } else {
     set CONF($file) ""
    }
}

In your finish function I renamed the function to submit (sorry) and rewrote it to make use of the new CONF variable.

The Answer

Of course most of this was unnecessary. I wanted to give you some interesting Tcl/Tk code to think about while at the same time solving your problem. At this point we should have the vocabulary to answer your question, though.

The reason your variables weren't printing out was because of initialization and scope. You should always use a -variable or -textvariable on widgets that you need to reference later. I generally initialize my referenced variables before building their containing widgets. So at the top of your file if you had done,

set colorimetric "-c"
ttk::checkbutton .f.colorimetric -variable colorimetric [...]

Then you would have been able to do, in the finish function,

puts $::colorimetric
Dylan
The choice of using an array is a good one. It's a very effective technique for keeping all the values for a form (dialog, property sheet, whatever you want to call it) together. However, from a style perspective I think I like the OP better. I understand your rationale but I think the original reads better. It's easier to see where the widgets are created and how they are laid out.
Bryan Oakley
You may have a good point there about the OP readability of widgets and layout. I didn't initially want to revise the grid section but when I switched to dynamic widget naming that started to feel dangerous, and I figured I'd better just automate it all.
Dylan