[Rd] [macosx] improving quartz & Aqua Tk behaviour outside of RGui

René J.V. Bertin rjvbertin at gmail.com
Mon May 11 16:17:37 CEST 2009


A couple of weeks ago I posted a trick in R-help on improving Quartz
behaviour in the command line version of R:
http://tolstoy.newcastle.edu.au/R/e6/help/09/04/12948.html .

Works with Aqua Tcl/Tk 8.5 too, but I discovered one annoying
side-effect. After having a Tk dialog open (and using it) for a while,
the R process starts eating more than 50% cpu on my PPC G4, using
either the 8.4 or the 8.5 Tcl/Tk libraries. (I'm currently running R
2.8.1 .)

This does NOT happen when running the exact same code in the same
commandline R version with the 8.4 X11 Tcl/Tk libraries, nor when I
run the Quartz version in R-GUI.

For completeness, here's the Tcl/Tk function:

dialog.test <- function(wait=FALSE)
{
	with3 <- function( data1, data2=.GlobalEnv, expr )
	{
		attach(data1)
		attach(data2)
		on.exit( detach(data1), add= FALSE )
		on.exit( detach(data2), add= TRUE )
	
		try( eval( substitute(expr), parent.frame() ) )
	}

	require(tcltk) || stop("tcltk support is absent")

	tt <- tktoplevel()
	tkwm.title(tt,"VG1 tests")
	tt.done <- tclVar("0")

	name <- paste('dialog.test',as.character(tt$ID), sep='')
	assign( name, tt, env=tdialog.env )

	dialogVals<-get("dialogVals", env=RJVB.env)
	data<-tclVar(dialogVals[1])
	crit<-tclVar(dialogVals[2])
	eval1st<-tclVar(dialogVals[9])
	func<-tclVar(dialogVals[3])
	args<-tclVar(dialogVals[4])
	args2<-tclVar(dialogVals[5])
	acomm<-tclVar(dialogVals[8])
	sumvar <- tclVar(dialogVals[7])
	done <- tclVar(0)
	savecmd<-tclVar(dialogVals[6]);
	devvar <- tclVar( dev.cur() )
	theData <- ""

	reset <- function()
	{
		tclvalue(data)<-""
		tclvalue(crit)<-""
		tclvalue(eval1st)<-""
		tclvalue(func)<-""
		tclvalue(args)<-""
		tclvalue(args2)<-""
		tclvalue(acomm)<-""
		tclvalue(sumvar)<-"0"
	}

	doSource <- function()
	{
		fileN <- tclvalue( tkgetOpenFile() )
		if( fileN != "" ){
			try( source(fileN) )
		}
	}

	dfInfo <- function(fnc)
	{
	## notice that tclvalue() is correct here, since it is the
	## string representation of xvar and yvar that is being
	## displayed in the entry fields

		dataf  <- tclvalue(data)
		crit  <- tclvalue(crit)
		eval1st  <- tclvalue(eval1st)

		if( is.null(crit) | !strlen(crit) ){
			theData <- paste( dataf )
			assign( "Selected.Cases", "", env=RJVB.env )
		}
		else{
			theData <- paste( "SelectCases(", dataf, ",\"", crit, "\")" )
		}
		cmd<-paste( fnc, "( ", theData, " )" )
		try(
			cmd<-parse( text=cmd )
		);
		print( paste( "###", cmd ) )
		print( try( eval(cmd, envir=.GlobalEnv) ) )
		cmd
	}

	build <- function()
	{
	## notice that tclvalue() is correct here, since it is the
	## string representation of xvar and yvar that is being
	## displayed in the entry fields

		dataf  <- tclvalue(data)
		crit  <- tclvalue(crit)
		eval1st  <- tclvalue(eval1st)
		func  <- tclvalue(func)
		args  <- tclvalue(args)
		args2  <- tclvalue(args2)
		acomm  <- tclvalue(acomm)
		summ <- as.logical(tclObj(sumvar))

		assign( "dialogVals",
c(dataf,crit,func,args,args2,dialogVals[6],tclvalue(sumvar), acomm,
eval1st ), env=RJVB.env )

		if( is.null(crit) | !strlen(crit) ){
			theData <- paste( dataf )
			assign( "Selected.Cases", "", env=RJVB.env )
		}
		else{
			theData <- paste( "SelectCases(", dataf, ",\"", crit, "\")" )
		}
		if( is.null(acomm) | is.na(acomm) | !strlen(acomm) ){
			acomm <- ""
		}
		else{
			acomm <- paste( ", add.comment=\"", acomm, "\"" )
		}
		if( summ ){
			cmd<-paste( "with3( ", theData, ", tkdial.env, summary(
last.dialog.result<-", func, "(", args, ",", args2, acomm, ") ) )" )
# 			cmd<-paste( "with2( ", theData, ", summary(
last.dialog.result<-", func, "(", args, ",", args2, acomm, ") ) )" )
		}
		else{
			cmd<-paste( "with3( ", theData, ", tkdial.env,
last.dialog.result<-", func, "(", args, ",", args2, acomm, ") )" )
# 			cmd<-paste( "with2( ", theData, ", last.dialog.result<-", func,
"(", args, ",", args2, acomm, ") )" )
		}
		assign( "Selected.Data", theData, env=RJVB.env )
		try(
			cmd<-parse( text=cmd )
		);
		cmd
	}

	saveIt <- function()
	{
		cmd<-savecmd  <- tclvalue(savecmd)

		assign( "dialogVals",
c(dialogVals[1],dialogVals[2],dialogVals[3],dialogVals[4],dialogVals[5],cmd,dialogVals[7],
dialogVals[9]), env=RJVB.env )
		try(
			cmd<-parse( text=savecmd )
		);
		cmd
	}

	doIt <- function(cmd="")
	{
		orgDev <- dev.cur()
		try( dev.set( tclvalue(devvar) ) )
		func  <- tclvalue(func)
		eval1st  <- tclvalue(eval1st)

		eval( parse( text="tkdial.env <- new.env()"), env=.GlobalEnv )

		if( !is.null(eval1st) & strlen(eval1st) ){
			try(
				eval1st <- parse( text=eval1st )
			);
			cat(deparse(eval1st,width=500),sep="\n")
			print(
				try( eval(eval1st, env=tkdial.env) )
			);
			ls( env=tkdial.env )
		}

		cat("### Command executed via Tk ###\n")
		cat(deparse(cmd,width=500),sep="\n")
		cat("### Output:\n")
		dialogVals<-get("dialogVals", env=RJVB.env)
		print( system.time( print( try( res<-eval(cmd, envir=.GlobalEnv) ) ) ) )
		if( func == 'aov' | func == 'aov.VG1' ){
			cat('\n')
			try( print( TukeyHSD(res, ordered=TRUE) ), silent=TRUE)
		}
		cat( paste( "### ----------- (", deparse(tclvalue(data),width=132),
") ----------- ###\n", sep="" ) )

		try( dev.set(orgDev) )

		eval( parse( text="rm(tkdial.env)" ), env=.GlobalEnv )
	}

	doQuit <- function()
	{
		dQ <- function()
		{
			tclvalue(done)<-"cancel"
			tkdestroy(tt)
			tt.done<-"1"
		}

		 # if the window is referenced in the windowlist environment, remove
the reference and then close
		if( exists(name, env=tdialog.env) ){
			w <- get(name, env=tdialog.env)
			if( !is.null(w) && class(w) == "tkwin" ){
				try( assign( name, NULL, env=tdialog.env ) )
				try( rm( list=name, envir=tdialog.env ) )
				dQ()
			}
		}
		else{
		 # if not, close too. Probably means that dQ() can sstill be called
recursively...
			dQ()
		}
		return(0)
	}

	data.entry <- tkentry(tt, textvariable=data, width=100)
	crit.entry <- tkentry(tt, textvariable=crit, width=100)
	eval1st.entry <- tkentry(tt, textvariable=eval1st, width=100)
	func.entry <- tkentry(tt, textvariable=func, width=100)
	args.entry <- tkentry(tt, textvariable=args, width=100)
	args2.entry <- tkentry(tt, textvariable=args2, width=100)
	acomm.entry <- tkentry(tt, textvariable=acomm, width=100)
	dev.entry <- tkentry(tt, textvariable=devvar, width=2)

	summ.cbut <- tkcheckbutton(tt,text="Print summary()", variable=sumvar)

	submit.but <- tkbutton(tt, text="submit",
				 command=function()doIt(build()) )

	savecmd.entry <- tkentry(tt, textvariable=savecmd, width=100)
	save.but <- tkbutton(tt, text="save",
				command=function()doIt(saveIt()) )

	reset.but <- tkbutton(tt, text="Reset", command=reset)
	source.but <- tkbutton(tt, text="Source", command=function()doSource() )
	cancel.but <- tkbutton(tt, text="Cancel", command=doQuit )
	
	names.but <- tkbutton(tt, text="names", command=function()dfInfo("names") )
	summary.but <- tkbutton(tt, text="summary",
command=function()dfInfo("summary") )

	tkgrid(tklabel(tt,text="Dataframe"), data.entry, names.but, columnspan=3 )
	tkgrid(tklabel(tt,text="Sel.Crit"), crit.entry, summary.but, columnspan=3 )
	tkgrid(tklabel(tt,text="Eval.1st"), eval1st.entry, columnspan=3 )
	tkgrid(tklabel(tt,text="Analysis"), func.entry, columnspan=3 )
	tkgrid(tklabel(tt,text="Variables"), args.entry, columnspan=3 )
	tkgrid(tklabel(tt,text="Options"), args2.entry, columnspan=3 )
	tkgrid(tklabel(tt,text="Comment"), acomm.entry, columnspan=3 )
	tkgrid(summ.cbut, tklabel(tt,text="Device"), dev.entry, sticky="e" ,
columnspan=3 )
	tkgrid(tklabel(tt,text="Save cmd"), savecmd.entry, columnspan=3 )
	tkgrid(submit.but, save.but, reset.but, source.but, cancel.but,
columnspan=2, sticky="w")

## capture destroy (e.g. from window controls
## otherwise the tkwait hangs with nowhere to go)
#	tkbind(tt, "<Destroy>", function()tclvalue(done)<-"quit")
	tkbind(tt, "<Destroy>", function()doQuit())
	tkbind(tt, "<Return>", function()doIt(build()) )
	tkbind(tt, "<Control-s>", function()doIt(saveIt()) )
	tkbind(tt, "<Control-S>", function()doSource() )

	.Tcl("update idletasks")

	if( wait ){
		while( tclvalue(done) != "cancel" ){
			tkwait.variable(done)
			doQuit()
		}
	}
# 	else{
# 		return(tt)
# 	}

## not necessary: button handlers do all the work, until tkdestroy().
# 	tkwait.variable(done)
#
# 	while( tclvalue(done)!= "cancel" ){
# 		if(tclvalue(done)=="quit") stop("aborted")
#
# 		if( tclvalue(done)=="save"){
# 			cmd <- saveIt()
# 		}
# 		else{
# 			cmd <- build()
# 		}
# 		cat("### Command executed via Tk ###\n")
# 		cat(deparse(cmd,width=132),sep="\n")
# 		cat("### Output:\n")
# 		dialogVals<-get("dialogVals", env=RJVB.env)
# 		print( try( eval.parent(cmd) ) )
# 		cat("### ----------------------- ###\n")
#
# 		tkwait.variable(done)
# 	}

# 	return(NULL)

}

On 2009-04-28, René J.V. Bertin <rjvbertin at gmail.com> wrote:

>  mkApp /Library/Frameworks/R.framework/Resources/bin/exec/R
<snip>
>  improves the behaviour of Quartz graphics windows, and of dialogs made
>  with TclTk (Aqua version 8.4), which for me now behave like under X11.
>  (i.e. as if controlled by a separate thread while the prompt remains
>  usable.)



More information about the R-devel mailing list