Search code examples
rloopshandlerlazy-evaluationgwidgets

Issues with assigning handlers through a loop


On the back of How to add a context menu to a `gframe`?, I am trying to create three different gframe objects with different context-menu handlers. To avoid code duplication, I'm doing this through a loop.

Consider this minimal example:

require(gWidgets2)
require(gWidgets2RGtk2)
w <- gwindow()
gg <- gvbox(cont=w)                   

f_lyt_ctab <- list()
l_lyt_ctab <- list()

h_ctab_clear  <- function(field.nr=NULL){
    stopifnot(!is.null(field.nr))
    print(field.nr)
}

lyt_ctab <- glayout(homogeneous=F, cont=gg, expand=TRUE, fill=T)
field.nms <- c("Row Fields", "Column Fields", "Values")
for(i in 1:3){
    lyt_ctab[1,i, expand=TRUE, fill=T] <- 
        f_lyt_ctab[[i]] <- gframe("", horizontal=FALSE,
                                  container=lyt_ctab, expand=TRUE, fill=T)
    ##have gframe with custom label (and context menu)
    l_lyt_ctab[[i]] <- glabel(field.nms[i])
    tooltip(l_lyt_ctab[[i]]) <- paste(
        "Right-click on", field.nms[i], "to clear field variables")
    print(i)
    print(field.nms[i])
    add3rdmousePopupMenu(l_lyt_ctab[[i]], 
                         list(a=gaction("Clear field", icon="clear", 
                                        handler=function(h, ...){
                                            h_ctab_clear(field.nr=i)
                                        })))
    f_lyt_ctab[[i]]$block$setLabelWidget(l_lyt_ctab[[i]]$block)         # the voodoo
    l_lyt_ctab[[i]]$widget$setSelectable(FALSE)           # may not be needed
}

The trouble is that for some reason the

handler=function(h, ...){ h_ctab_clear(field.nr=i) })

does not seem to get passed the correct i value. It is always 3. So whichever context-menu is accessed, it's only the h_ctab_clear(field.nr=3) that gets executed.

I'm stumped if for the simple reason that the gframe tooltips are all correct. But not the handler associated with each context menu.

I'm suspecting some scope or similar issue with h_ctab_clear(field.nr=i), but I'm not sure what is wrong?


Solution

  • I would guess that add3rdmousePopupMenu is probably taking your handler function and evaluating it in a different context. At that point, it can't resolve i because it's a free variable in the function body and the original enclosure environment is no longer available. One possible solution is to explicitly create an enclosure that hold the i value. To make this easier, we can create a helper function.

    makehandler <- function(i) {
        force(i); 
        function(h, ...){
            h_ctab_clear(field.nr=i)
        }
    }
    

    This function will enclose i and then return a function that you can use as a handler. Then you can use it like

    add3rdmousePopupMenu(l_lyt_ctab[[i]], 
        list(a=gaction(
            "Clear field", 
            icon="clear", 
            handler=makehandler(i)
        ))
    )