0

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?

Community
  • 1
  • 1
landroni
  • 2,902
  • 1
  • 32
  • 39
  • I have no idea what this code is supposed to do since it doesn't appear to be [reproducible](http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) (ie, when I paste it into R i get a bunch of errors starting with "No toolkit packages are installed" from `gwindow`). But ignoring that for now, i'm guessing the problem is due to lazy evaluation of parameter values. Formally doing something like `force(i)` will help turn the promise value if `i` into an actual value but where you would put that in this case is unclear. Please make your code runnable. – MrFlick Sep 07 '14 at 14:27
  • The code is reproducible. You probably need to `install.packages("gWidgets2RGtk2")`. As for your comment, I was also suspecting something related to " lazy evaluation" or similar, but I'm not sure how to work around that. – landroni Sep 07 '14 at 14:31
  • Sure, will do so. Usually it works fine with just `require(gWidgets2)`, but obviously it fails when user hasn't installed `gWidgets2RGtk2`. Will be more careful next time. – landroni Sep 07 '14 at 14:35
  • For Mac, you would need to have the development libraries of Gtk2 installed. (On Ubuntu Linux it's `libgtk2.0-dev`.) I'll test your suggestion and get back. – landroni Sep 07 '14 at 14:49
  • Hmm, doesn't seem to work. I tried your suggestion exactly, but nothing seems to be happening. Not even an error message. – landroni Sep 07 '14 at 15:24
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60783/discussion-between-mrflick-and-landroni). – MrFlick Sep 07 '14 at 15:27

1 Answers1

1

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)
    ))
)
MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Sometimes this scoping issue can be resolved by using `sapply` in place of the for loop, as then the `i` value is within the environment of the function call. The `force` trick is another possible solution. – jverzani Sep 08 '14 at 00:17