[R-gui] Idle/timer callbacks in tk widgets

Duncan Murdoch murdoch at stats.uwo.ca
Tue Jan 9 15:04:13 CET 2007


On 1/9/2007 8:08 AM, Stephen Eglen wrote:
>  > 
>  > For the archives (and for others if interested) I'll send my test
>  > script tomorrow to the list.
> 
> Here it is.  

I did a similar thing in the tkrgl package (recently uploaded to CRAN), 
to control rotating rgl plots.

A few differences:

I queried the system clock to set the amount of rotation, so it would 
appear to rotate at a continuous rate even if the update wasn't serviced 
regularly.

I used tcl("after", "idle", onIdle) to trigger the next update.  This is 
supposed to avoid callbacks when the system is busy, but it still makes 
a bit too much impact on the system.  Probably the best low impact way 
would be to follow a fixed delay with an "idle" delay, to guarantee a 
minimum delay time.

I defined all the event handling functions within one wrapper function, 
so they had access to its variables through lexical scoping, without 
exposing them all to the whole world.

Here's some of the code (just for the "up" rotation, there's a lot of 
repetitiveness for other kinds of rotation.  The repetition could be 
reduced by just setting the rotation axis and direction, but it doesn't 
really seem worth the effort).  Improvements would be welcome.

Duncan Murdoch


spinControl <- function(base, dev = rgl.cur()){

# This creates a control that can be put in the tk container called base

	slider <- tclVar(100)  # this controls the rotation rate

	continuous <- tclVar(0) # this controls whether rotation continues on 
its own
	
	buttonPress <- NULL
	direction <- NULL
	
	lastTime <- proc.time()[3]  # this records the last time the display 
was updated

	timeDiff <- 0

         # Here's a typical rotation function:

	rotateUp <- function(){
		angle <- timeDiff*as.numeric(tclObj(slider))*pi/180
		par3d(userMatrix = rotationMatrix(-angle, 1,0,0) %*% par3d("userMatrix"))
	}


	rotate <- function(){
	    	old <- rgl.cur()
	    	on.exit(rgl.set(old))	
	    	if (buttonPress) {
		    if ((currentTime <- proc.time()[3]) > lastTime) {
			timeDiff <<- currentTime - lastTime
			lastTime <<- currentTime
			for (device in dev) {
			    rgl.set(device)
			    if (direction == "up")
				    rotateUp()

...
			}
		    }
		    tcl("after",5,rotate) # rotation is continuous as long as the 
button is pressed.
	    }

	}

	# rotation button callback functions
	# note that "..." argument is necessary
	upButtonPress <- function(...){
		buttonPress <<- TRUE
		lastTime <<- proc.time()[3]
		direction <<- "up"
		rotate()
	}

...

         onIdle <- function(...){
         	buttonPress <<- TRUE
         	rotate()
         	buttonPress <<- FALSE
		if (as.numeric(tclObj(continuous)))
		    tcl("after", "idle", onIdle)
	}

The idle rotation only happens if the continuous variable is non-zero 
when a button is released.  It's connected to a checkbox.
      	
	buttonRelease <- function(...){
		buttonPress <<- FALSE
		if (as.numeric(tclObj(continuous)))
		    tcl("after", "idle", onIdle)
	}

  ....
}

Duncan Murdoch



More information about the R-SIG-GUI mailing list