[R-gui] tkEditMatrix()
jhallman@frb.gov
jhallman at frb.gov
Tue Sep 20 18:31:23 CEST 2005
Philippe,
I wrote this matrix editor that uses Tktable and sent it to James Wettenhall,
suggesting that he might want to add it to his tcltk examples web page. James
replied that he was no longer maintaining those pages, but that you had
volunteered to take them over. James also suggested I send it to the
R-SIG-GUI list, so I'm doing that as well.
The code is public domain, so feel free to do anything you like with it.
Jeff Hallman
dim.tclArray <- function(ta){
nms <- grep(",", names(ta), value = T)
if(length(nms) == 0) return(c(0,0))
c(max(as.numeric(gsub(",.*", "", nms))),
max(as.numeric(gsub(".*,", "", nms)))) + 1
}
tkEditMatrix <- function(x, title="Matrix Editor",
header = NULL,
maxHeight = 600, maxWidth = 800,
fontsize = 17,
...){
tclRequire("Tktable")
.Tcl(paste("option add *Table.font {courier", fontsize, "bold}"))
old <- options(scipen = 7)
on.exit(options(old))
makeCharMat <- function(x){
## make sure it's a character matrix
mat <- matrix(unlist(x), nrow = nrow(as.matrix(x)))
dm <- dim(mat)
## check for row and column names
hasRownames <- length(rn <- rownames(x)) > 0
hasColnames <- length(cn <- colnames(x)) > 0
## fake row and column names if they aren't there
if(!hasRownames) rn <- paste("[", 1:nrow(x), ",]", sep = "")
if(!hasColnames) cn <- paste("[,", 1:ncol(x), "]", sep = "")
## format the columns
mat[] <- apply(unclass(mat), 2, format, justify = "right")
mat <- rbind(cn, mat)
mat <- cbind(c("", rn), mat)
mat
}
fillTclArrayFromCharMat <- function(ta, cm){
## cm[,1] contains column names, while cm[1,] has rownames
## cm[1,1] is ignored
for(j in 2:ncol(cm)) ta[[0, j-1]] <- as.tclObj(cm[1, j], drop = T)
for(i in 2:nrow(cm))
for(j in 1:ncol(cm))
ta[[i-1, j-1]] <- as.tclObj(cm[i, j], drop = T)
}
tA <- tclArray()
cmat <- makeCharMat(x)
fillTclArrayFromCharMat(tA, cmat)
tt <- tktoplevel()
tkwm.title(tt,title)
colwidths <- apply(cmat, 2, function(x) max(nchar(x)) + 1 )
nTableCols <- ncol(cmat)
if((moreWidth <- 60 - sum(colwidths)) > 0){
addEach <- moreWidth %/% length(colwidths)
if(addEach < 5) colwidths <- colwidths + addEach + 1
else nTableCols <- nTableCols + ceiling(moreWidth/10)
}
tktable <- tkwidget(tt, "table",
variable = tA,
rows = nrow(cmat), cols = nTableCols,
titlerows = 1, titlecols = 1, selecttitle = 1,
anchor = "e", multiline = 0,
selectmode = "extended",
rowseparator = dQuote("\n"),
colseparator = dQuote("\t"),
background = "white",
maxheight = maxHeight, maxwidth = maxWidth,
xscrollcommand = function(...) tkset(xscr,...),
yscrollcommand = function(...) tkset(yscr,...))
xscr <-tkscrollbar(tt, orient = "horizontal",
command = function(...)tkxview(tktable,...))
yscr <- tkscrollbar(tt, command = function(...)tkyview(tktable,...))
## set column widths
for(i in 1:ncol(cmat)) tcl(tktable, "width", i-1, colwidths[i])
## rebind the Backspace key, which somehow gets messed up
string <- "bind Table <BackSpace> {
set ::tk::table::Priv(junk) [%W icursor]
if {[string compare {} $::tk::table::Priv(junk)] && $::tk::table::Priv(junk)} {
%W delete active [expr {$::tk::table::Priv(junk)-1}]
}}"
.Tcl(string)
## internal functions for buttons
activeRow <- function() as.numeric(tkindex(tktable, "active", "row"))
activeCol <- function() as.numeric(tkindex(tktable, "active", "col"))
undoEdits <- function(){
ta <- tclArray()
fillTclArrayFromCharMat(ta, cmat)
assign("tA", ta, inherits = T)
tkconfigure(tktable, variable = tA)
}
finish <- function() tkdestroy(tt)
cancel <- function(){
undoEdits()
tkdestroy(tt)
}
insertRow <- function(){
row <- activeRow()
col <- activeCol()
tkinsert(tktable, "rows", row, 1)
newCell <- paste(row + 1, col, sep = ",")
tkactivate(tktable, newCell)
tksee(tktable, newCell)
}
insertCol <- function(){
row <- activeRow()
col <- activeCol()
tkinsert(tktable, "cols", col, 1)
newCell <- paste(row, col + 1, sep = ",")
tkactivate(tktable, newCell)
tksee(tktable, newCell)
}
deleteRow <- function(){
if((row <- activeRow()) != 0)
tkdelete(tktable, "rows", row, 1)
}
deleteCol <- function(){
if((col <- activeCol()) != 0)
tkdelete(tktable, "cols", col, 1)
}
copyRow <- function(){
src <- activeRow()
if(src != 0){
insertRow()
dst <- activeRow()
for(j in 0:(ncol(tA)-1)) tA[[dst,j]] <- tA[[src, j]]
}
}
copyCol <- function(){
src <- activeCol()
if(src != 0){
insertCol()
dst <- activeCol()
for(i in 0:(nrow(tA)-1)) tA[[i,dst]] <- tA[[i,src]]
}
}
finishButton <- tkbutton(tt, text = "Finish", command = finish)
cancelButton <- tkbutton(tt, text = "Cancel", command = cancel)
undoEditsButton <- tkbutton(tt, text = "Undo Edits", command = undoEdits)
insertRowButton <- tkbutton(tt, text = "Insert Row", command = insertRow)
copyRowButton <- tkbutton(tt, text = "Copy Row", command = copyRow)
deleteRowButton <- tkbutton(tt, text = "Delete Row", command = deleteRow)
insertColButton <- tkbutton(tt, text = "Insert Col", command = insertCol)
copyColButton <- tkbutton(tt, text = "Copy Col", command = copyCol)
deleteColButton <- tkbutton(tt, text = "Delete Col", command = deleteCol)
## Layout
if(length(header) > 0){
for(label in header)
tkgrid(tklabel(tt, text = label), columnspan = 7, sticky = "nw")
}
tkgrid(tktable, yscr, columnspan = 8)
tkgrid.configure(tktable, sticky = "news")
tkgrid.configure(yscr, sticky = "nsw")
tkgrid(xscr, sticky = "new", columnspan = 8)
tkgrid(insertRowButton, copyRowButton, deleteRowButton, sticky = "news")
tkgrid(insertColButton, copyColButton, deleteColButton,
"x", cancelButton, undoEditsButton, finishButton, sticky = "news")
tkgrid.columnconfigure(tt, 3, weight = 1)
tkgrid.rowconfigure(tt, length(header), weight = 1)
tkactivate(tktable, "0,0")
tktag.configure(tktable, "active", background = "lightyellow2")
tktag.configure(tktable, "title", state = "normal")
tkgrab.set(tt)
tkfocus(tt)
tkwait.window(tt)
outMat <- matrix("", nrow = nrow(tA), ncol = ncol(tA))
for(i in 1:nrow(outMat))
for(j in 1:ncol(outMat)){
val <- tA[[i-1,j-1]]
if(is.null(val)) val <- ""
else val <- tclvalue(val)
outMat[i,j] <- val
}
## recover row and column names
rn <- outMat[,1][-1]
cn <- outMat[1,][-1]
outMat <- outMat[-1, -1, drop = F]
## ignore badd and/or NA row and column names
badRownames <- c(grep("\\[.*\\]", rn), (1:length(rn))[is.na(rn)])
if(length(badRownames) != length(rn)){
rn[badRownames] <- ""
rownames(outMat) <- rn
}
badColnames <- c(grep("\\[.*\\]", cn), (1:length(cn))[is.na(cn)])
if(length(badColnames) != length(cn)){
cn[badColnames] <- ""
colnames(outMat) <- cn
}
mode(outMat) <- mode(x)
Sys.sleep(0.1)
return(outMat)
}
More information about the R-SIG-GUI
mailing list