Detecting keypress

For a Julia+Gtk course I’m writing, I need to process keys when pressed. This stripped-down example shows that this only works if the callback function, keycall, is defined inside, not outside, the keypresswindow function. Can anybody explain how to make this work when keycall is defined outside? Thanks for any help.
MacOS 10.14.4, Julia 1.5

# keytest.jl: Detect typed keys
using Gtk
win = GtkWindow("Keypress Test")
# Does not work if keycall is defined only here
    function keycall(w, event)
        ch = Char(event.keyval)
        tcount += 1
        println("$tcount: $ch")
    end
    signal_connect(keycall, win, "key-press-event")
#
function keypresswindow()
    tcount = 0
    println("Click on window, then press any keys")
# Works only if keycall is defined here; change true to false to demonstrate
  if true
    function keycall(w, event)
        ch = Char(event.keyval)
        tcount += 1
        println("$tcount: $ch")
    end
    signal_connect(keycall, win, "key-press-event")
  end
end
keypresswindow()
return

In your version where keycall is defined “outside” (i.e. in the global, module scope) you have an issue regarding (write) access to a (non-existent) global variable tcount.

This issue is solved in your other version, because tcount now refers to a well-defined local variable in keypresswindow, and keycall is a closure that closes over that variable.

A way to make it work with a global function keycall would be to explicitly declare tcount as a global, mutable variable (which you’d rather make constant). In the example below, I made tcount a reference; note in particular how its value is always accessed as tcount[]:

using Gtk
win = GtkWindow("Keypress Test")

# tcount is a global variable, of type Ref{Int}
# the value it refers to is accessed as tcount[]
const tcount = Ref(0)

# keycall is a regular (global) function
function keycall(w, event)
    @info "Key press event"
    ch = Char(event.keyval)
    tcount[] += 1
    println("$(tcount[]): $ch")
end

signal_connect(keycall, win, "key-press-event")

Or you could stick with the current solution of having keycall close over a local variable (which makes it possible to access to tcount more naturally). One way to do that would look like:

using Gtk
win = GtkWindow("Keypress Test")

const keycall = let
    # tcount is a regular local variable (defined in the scope of the `let` block
    tcount = 0

    # keycall is now a closure over tcount
    function (w, event)
        @info "Key press event"
        ch = Char(event.keyval)
        tcount += 1
        println("$tcount: $ch")
    end
end

signal_connect(keycall, win, "key-press-event")
4 Likes

Probably stupid question, but, in OP case (function keypresswindow), both tcount and key all are local to keypresswindow, so, how come they are not discarded after returning? How the compiler knows it has to capture them?

Names already existing in the containing scope are assigned into there. Python uses the nonlocal keyword for this behavior but doing it automatically is traditional in Lisp which Julia inherits from.

Detailed thread about this:

2 Likes

Thank you for the enlightening reference!

Thanks for catching my error. I’m somewhat surprised that neither REPL nor the compiler flagged the error inside the function, since the bare statement a+=1 is immediately flagged by the REPL… but then, I didn’t catch it either. I’m grateful to those who responded.

1 Like