• Struggling with namespaces, scopes and selections

    From Luc@luc@sep.invalid to comp.lang.tcl on Mon Mar 11 16:49:10 2024
    From Newsgroup: comp.lang.tcl

    Problem #1:

    spinbox:
    Command-Line Name: -textvariable
    Specifies the name of a global variable. The value of the variable is
    a text string to be displayed inside the widget; if the variable value
    changes then the widget will automatically update itself to reflect the
    new value. The way in which the string is displayed in the widget
    depends on the particular widget and may be determined by other
    options, such as -anchor or -justify.

    I dislike that. When the dialog is launched, it has a spinbox that is
    supposed to display an initial value. While I was working in the global
    scope, it worked fine. Then I put everything into a namespace and now
    that variable doesn't work anymore, even if I refer to it by its
    complete path, i.e. including namespace.

    So I am forced to make that variable global to make the widget look
    correctly. This is supposed to be a package. What if my global variable
    clashes with another global variable in someone else's code?

    What do you think of this? Any solutions?

    Optional: video showing the problem:
    https://0x0.st/HhyO.mp4



    Problem #2:

    I have this binding inside a namespace. Its command became too long
    so I decided to make a proc out of it.

    proc p.home
    bind $namebox <Home> p.home

    It doesn't work. The compiler can't find the proc.

    So I use its complete path:

    bind $namebox <Home> typychooser::p.home

    Now it can find the proc, but can't find the variable.

    So I make the proc run everything in the variable's namespace with
    eval. But the compiler still can't see the variable.

    1. Why?

    2. How do I get out of this straitjacket?

    Optional: video showing the problem:
    https://0x0.st/HhyW.mp4



    Problem #3:

    If you notice I'm messing with put statements, that is my attempt to
    debug and fix two selection problems:

    1. When I force-select an item such as pressing Home to select the
    first item, whatever was selected before still is sort of selected.
    It still has the dotted lines around it and the navigation is still
    anchored to the old selection.

    2. When I tab from one list box to the other, I lose the selection
    in the previous one.

    Can you offer any useful comments on these problems?

    Optional: video showing the problem:
    https://0x0.st/HhyJ.mp4
    --
    Luc


    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Rich@rich@example.invalid to comp.lang.tcl on Mon Mar 11 20:49:34 2024
    From Newsgroup: comp.lang.tcl

    Luc <luc@sep.invalid> wrote:
    Problem #1:

    spinbox:
    Command-Line Name: -textvariable
    Specifies the name of a global variable. The value of the variable is
    a text string to be displayed inside the widget; if the variable value changes then the widget will automatically update itself to reflect the
    new value. The way in which the string is displayed in the widget
    depends on the particular widget and may be determined by other
    options, such as -anchor or -justify.

    I dislike that. When the dialog is launched, it has a spinbox that is supposed to display an initial value. While I was working in the global scope, it worked fine. Then I put everything into a namespace and now
    that variable doesn't work anymore, even if I refer to it by its
    complete path, i.e. including namespace.

    So I am forced to make that variable global to make the widget look correctly. This is supposed to be a package. What if my global variable clashes with another global variable in someone else's code?

    What do you think of this? Any solutions?

    Optional: video showing the problem:
    https://0x0.st/HhyO.mp4

    Replying to just one "problem" at a time.

    This:

    -textvariable typychooser::SpinboxFontSize

    is not a global variable

    This:

    -textvariable ::typychooser::SpinboxFontSize

    is a global variable (well, pedantically, it is a fully qualified
    namespace variable that can be dereferenced and found from the global
    scope, but in essence, it works the same as "a global variable").

    The leading :: is important.

    Note, above, I am assuming that the namespace "typychooser" is actually defined in :: and not in some other parent namespace.

    The "namespace independent" way to point a Tk widget -textvariable
    option at a namespace variable (in the current namespace) is to do it
    this way:

    -textvariable [namespace current]::SpinboxFontSize

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Rich@rich@example.invalid to comp.lang.tcl on Mon Mar 11 20:53:51 2024
    From Newsgroup: comp.lang.tcl

    Luc <luc@sep.invalid> wrote:
    Problem #2:

    I have this binding inside a namespace. Its command became too long
    so I decided to make a proc out of it.

    proc p.home
    bind $namebox <Home> p.home

    It doesn't work. The compiler can't find the proc.

    So I use its complete path:

    bind $namebox <Home> typychooser::p.home

    Now it can find the proc, but can't find the variable.

    So I make the proc run everything in the variable's namespace with
    eval. But the compiler still can't see the variable.

    1. Why?

    Bindings execute at the global level, outside of any namespace, so you
    can't point a binding at a proc that is in a namespace without a bit of
    extra work.

    2. How do I get out of this straitjacket?

    Read the documentation for "namespace code" in the namespace manpage.

    It provides the "bit of extra work" you are missing.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Luc@luc@sep.invalid to comp.lang.tcl on Mon Mar 11 19:49:09 2024
    From Newsgroup: comp.lang.tcl

    On Mon, 11 Mar 2024 20:53:51 -0000 (UTC), Rich wrote:

    1. Why?

    Bindings execute at the global level, outside of any namespace, so you
    can't point a binding at a proc that is in a namespace without a bit of >extra work.

    2. How do I get out of this straitjacket?

    Read the documentation for "namespace code" in the namespace manpage.

    It provides the "bit of extra work" you are missing.


    I don't get it.

    set [namespace current]::runhome [namespace code {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }]
    puts [set [namespace current]::runhome]

    bind $namebox <Home> {eval $::typychooser::runhome}


    So I run the code and the puts line (outside the proc) outputs this:

    ::namespace inscope ::typychooser {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }

    But when I press Home...


    can't read "namebox": no such variable
    while executing
    "$namebox itemconfigure 0"
    (in namespace inscope "::typychooser" script line 2)
    invoked from within
    "::namespace inscope ::typychooser {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebo..."
    ("eval" body line 1)
    invoked from within
    "eval $::typychooser::runhome"
    (command bound to event)


    I could fix this now merely replacing $namebox with the hard coded
    path of the widget. But I still want to understand this problem.
    --
    Luc


    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Luc@luc@sep.invalid to comp.lang.tcl on Mon Mar 11 20:02:53 2024
    From Newsgroup: comp.lang.tcl

    On Mon, 11 Mar 2024 19:49:09 -0300, Luc wrote:


    What I also don't understand:

    Regarding problem #1, I followed your guidance:

    set [namespace current]::SpinboxFontSize $guifontsize
    ttk::spinbox $pf.sizespinbox
    $pf.sizespinbox configure -textvariable [namespace current]::SpinboxFontSize

    And now the number is shown as expected. OK. Solved.

    But wait a minute. Look at those variables...

    Back to problem #2:

    set [namespace current]::runhome [namespace code {
    puts "current [namespace current]"
    puts [info vars]
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }]

    output:

    current ::typychooser
    SpinboxFontSize runhome version tcl_rcFileName tcl_version argv0 argv tcl_interactive tk_library tk_version auto_path errorCode
    tk_strictMotif errorInfo auto_index env tcl_pkgPath tcl_patchLevel
    argc tk_patchLevel tcl_library tcl_platform

    Why is SpinboxFontSize found in the [info vars] list but not pf,
    guifontsize and many others? They all exist within the scope of the
    same namespace.

    Very confusing.
    --
    Luc


    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Luc@luc@sep.invalid to comp.lang.tcl on Mon Mar 11 21:11:57 2024
    From Newsgroup: comp.lang.tcl

    On Mon, 11 Mar 2024 20:02:53 -0300, Luc wrote:

    Why is SpinboxFontSize found in the [info vars] list but not pf,
    guifontsize and many others? They all exist within the scope of the
    same namespace.

    Very confusing.


    Never mind. Now I see I did something stupid and I know how to fix it.

    Thank you again.
    --
    Luc


    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Rich@rich@example.invalid to comp.lang.tcl on Tue Mar 12 00:52:03 2024
    From Newsgroup: comp.lang.tcl

    Luc <luc@sep.invalid> wrote:
    On Mon, 11 Mar 2024 20:53:51 -0000 (UTC), Rich wrote:

    1. Why?

    Bindings execute at the global level, outside of any namespace, so you >>can't point a binding at a proc that is in a namespace without a bit of >>extra work.

    2. How do I get out of this straitjacket?

    Read the documentation for "namespace code" in the namespace manpage.

    It provides the "bit of extra work" you are missing.


    I don't get it.

    set [namespace current]::runhome [namespace code {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }]
    puts [set [namespace current]::runhome]

    bind $namebox <Home> {eval $::typychooser::runhome}


    So I run the code and the puts line (outside the proc) outputs this:

    ::namespace inscope ::typychooser {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }

    But when I press Home...


    can't read "namebox": no such variable
    while executing
    "$namebox itemconfigure 0"
    (in namespace inscope "::typychooser" script line 2)
    invoked from within
    "::namespace inscope ::typychooser {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebo..."
    ("eval" body line 1)
    invoked from within
    "eval $::typychooser::runhome"
    (command bound to event)

    In the "script" you attached to namespace code, you never defined a
    "namebox" variable.

    What you are fighting here is why the normal response on Usenet is
    almost always:

    Put your code in a proc, and call that proc from the binding (or the
    -command option).

    If you had a proc for your code:

    namespace eval ::wherever-you-are {
    proc handle-namebox {namebox} {
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }
    }

    And in the binding, you did:

    bind $namebox <Home> [namespace code [list handle-namebox %W]]

    You should get what you want.

    I could fix this now merely replacing $namebox with the hard coded
    path of the widget. But I still want to understand this problem.

    You didn't follow the normal advice, put the "code" in a proc, call the
    proc from the binding. It is much easier to handle variable scoping
    issues inside a proc than it is directly within a binding script.

    A quick, rough and dirty, demo:

    #!/usr/bin/wish

    namespace eval ::something {
    proc handle-namebox {namebox} {
    puts "handle-namebox: namebox='$namebox'"
    puts "current namespace I am in is '[namespace current]'"
    }

    bind . <Home> [namespace code [list handle-namebox %W]]
    }

    Running that, giving it keyboard focus, and hitting Home while it has
    focus results in this on stdout:

    handle-namebox: namebox='.'
    current namespace I am in is '::something'

    And, lest you think you need a different proc for each widget in a 1:1
    ratio, in general you do not (unless you have very different "actions" triggered from the same keypress on different widgets). By using the
    %W expansion in the binding, you can pass the "widget name" to the
    proc, and then one single proc can work with plural widgets, because Tk
    will tell it which widget it should work upon. Note that my 'quick and
    dirty' example simply receives "." as the name of the 'widget' that the binding was triggered on. That is because it is "quick and dirty".

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Rich@rich@example.invalid to comp.lang.tcl on Tue Mar 12 01:07:42 2024
    From Newsgroup: comp.lang.tcl

    Luc <luc@sep.invalid> wrote:
    On Mon, 11 Mar 2024 19:49:09 -0300, Luc wrote:


    What I also don't understand:

    Regarding problem #1, I followed your guidance:

    set [namespace current]::SpinboxFontSize $guifontsize
    ttk::spinbox $pf.sizespinbox
    $pf.sizespinbox configure -textvariable [namespace current]::SpinboxFontSize

    And now the number is shown as expected. OK. Solved.

    But wait a minute. Look at those variables...

    Back to problem #2:

    set [namespace current]::runhome [namespace code {
    puts "current [namespace current]"
    puts [info vars]
    puts [list [$namebox itemconfigure 0]]
    $namebox selection clear 0 end
    $namebox selection set 0
    $namebox selection anchor 0
    $namebox see 0
    }]

    output:

    current ::typychooser
    SpinboxFontSize runhome version tcl_rcFileName tcl_version argv0 argv tcl_interactive tk_library tk_version auto_path errorCode
    tk_strictMotif errorInfo auto_index env tcl_pkgPath tcl_patchLevel
    argc tk_patchLevel tcl_library tcl_platform

    Why is SpinboxFontSize found in the [info vars] list but not pf,
    guifontsize and many others? They all exist within the scope of the
    same namespace.

    Without the full code context I can only guess. But my guess would be:

    SpinboxFontSize is a namespace variable.

    pf, guifontsize, etc. are proc locals?

    Proc locals don't exist "in a namespace" they exist only within the
    given proc.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Emiliano@emil.gavilan@gmail.com to comp.lang.tcl on Tue Mar 12 12:16:18 2024
    From Newsgroup: comp.lang.tcl

    On Mon, 11 Mar 2024 16:49:10 -0300
    Luc <luc@sep.invalid> wrote:

    To add to what Rich already explained...

    Years ago I realized that writing Tk code inside namespaces requires some additional tools to reduce the amount of boilerplate here and there, so
    I now use the following helper procs:

    [fqcmd]. Fully qualifies a command prefix. Uses [namespace which].
    Yes, I never used inline scripts nor use [namespace code] (but keep
    reading about lambda). Used by both [bind] command and -command option
    to widgets.
    Example usage:
    $button configure -command [fqcmd someaction arg1 arg2 ...]
    bind $w <Configure> [fqcmd configureproc %W %w %h]
    # calling a TclOO method as callback
    $button configure -command [fqcmd my method arg1 arg2 ...]

    [fqvar]. Fully qualifies a variable name. Uses [namespace which -variable] Example usage:
    $label configure -textvariable [fqvar nsvarname]
    # if arrays are used, this is how to use it
    # (see https://core.tcl-lang.org/tcl/tktview/472113ffffffffffffff)
    $button configure -textvariable [fqvar nsary](somekey)

    [lambda]. In cases where a *very* simple action is to be executed, a
    lambda (defaulting to [namespace current], or use [lambda@] from tcllib)
    might be enough.
    Example usage:
    $button configure -command [lambda {b} {
    variable counter
    puts "The button $b has been pressed [incr counter] times"
    } $button]

    In case of of using lambdas with [bind], take percent expansion in consideration.

    In the first two cases, the command must already exists and the variable
    must already be declared (with [variable]) when [fq{var|cmd}] is called.

    As always with programming, YMMV.
    --
    Emiliano
    --- Synchronet 3.20a-Linux NewsLink 1.114