Nothing
# TODO
# 2. Make selection caption work
# 4. Add menu
# 5. Autosize window
# 7. Fix t.to error
# 8. Remove cursor when not edit mode
# 9. Doesn't select on factor matches properly
# 10. Row key press paint update doesn't work
# 11. Page-down etc doesn't call selection
# Written by Tom Taverner <t.taverner@gmail.com>
# for the U.S. Department of Energy (PNNL, Richland, WA, USA)
# Website: http://omics.pnl.gov/software
#
# Notice: This computer software was prepared by Battelle Memorial Institute,
# hereinafter the Contractor, under Contract No. DE-AC05-76RL0 1830 with the
# Department of Energy (DOE). All rights in the computer software are reserved
# by DOE on behalf of the United States Government and the Contractor as
# provided in the Contract.
#
# NEITHER THE GOVERNMENT NOR THE CONTRACTOR MAKES ANY WARRANTY, EXPRESS OR
# IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.
#
# This notice including this sentence must appear on any copies of this computer
# software.
#' A package for an editing data frames for RGtk2. ImIproves on base edit.data.frame function found in utils
#' @name RGtk2DfEdit-package
#' @docType package
# Bugs:
# Prints Logical NA as TRUE
# The cell render function is a kludge
# Some platforms don't paint column selection, I can't reproduce this
require(RGtk2)
"\\001\\011\\000" # wide dots
"\\001\\011\\000" # dashes
############################################################
# Style RC
############################################################
# don't make the focus line width bigger than the v-separator
style <- 'style "treeview-style" {
GtkTreeView::horizontal-separator = 0
GtkTreeView::vertical-separator = 0
GtkTreeView::focus-line-width = 0
GtkTreeView::focus-line-pattern = "\\000\\000\\000"
GtkTreeView::grid-line-pattern = "\\000\\000\\000"
GtkTreeView::grid-line-width = 0
bg_pixmap[NORMAL] = "<none>"
bg_pixmap[INSENSITIVE] = "<none>"
bg_pixmap[SELECTED] = "<none>"
bg_pixmap[ACTIVE] = "<none>"
bg_pixmap[PRELIGHT] = "<none>"
}
class "GtkTreeView" style "treeview-style"
style "paned-style" {
GtkPaned::handle-size = 5
}
class "GtkPaned" style "paned-style"'
style <- 'style "treeview-style" {
GtkTreeView::focus-line-width = 0
}
class "GtkTreeView" style "treeview-style"
style "paned-style" {
GtkPaned::handle-size = 5
}
class "GtkPaned" style "paned-style"'
gtkRcParseString(style)
DATA_OBJECTS = c("data.frame", "matrix", "array")
#
#old_warning <- warning
#12345.67 is "12,345.67" in the US, "12 345,67" in France and "12.345,67"
STACK_LIMIT <- 1E8
# Don't bother drawing row selection rectangles if there are more than this
MAX_ROWS_TO_DRAW_SELECTION <- 1000
VERSION_STRING <- "version 0.6.1"
COLUMN_OFFSET <- 1
# Optionally, we include a blank row at the bottom
# 0 if it isn't, 1 if it is
EXTRA_ROW <- 0
ToInternalColIdx <- function(x) x+COLUMN_OFFSET
ToExternalColIdx <- function(x) x-COLUMN_OFFSET
# replace these with "" in pretty_print
if(.Platform$OS.type == "windows") {
PLATFORM_OS_TYPE <- "windows"
} else if (.Platform$OS.type == "unix"){
if (Sys.info()["sysname"] == "Darwin")
PLATFORM_OS_TYPE <- "mac"
else{
PLATFORM_OS_TYPE <- "unix" }
}
# for pasting
if(PLATFORM_OS_TYPE == "windows"){
NEWLINE_CHAR <- "\r\n"
HEADER_BOX_MARGIN <- 2 # how to get this?
} else {
NEWLINE_CHAR <- "\n"
HEADER_BOX_MARGIN <- 4
}
DO_CAIRO <- TRUE
if (Sys.info()["sysname"] == "Darwin")
DO_CAIRO <- FALSE
DEFAULT_COLNAMES <- c(LETTERS, sort(as.vector(outer(LETTERS, LETTERS, FUN=paste, sep=""))))
MergeAdjacent <- function(v, warn=T){
dv <- diff(v)
if(warn && sum(dv < 1))
warning("Vector is not increasing")
if(length(v)){
w1 <- which(dv != 1)
cbind(start=v[c(1, w1+1)],
end=v[c(w1, length(v))])
#,
#length=1+length(w1))
} else {
cbind(start=integer(0), end=integer(0))#, length=0)
}
}
# http://www.mail-archive.com/r-help@r-project.org/msg38009.html
make.call = function(func, my.arg){
Call <- match.call()
fn <- deparse(substitute(func))
Call[[1]] <- as.name(fn)
Call$func <- NULL
Call$my.arg <- NULL
my.formals <- formals(func)
Call[[2]] <- as.name(deparse(substitute(my.arg)))
return(Call)
}
findxInParseTree <- function(txt){
.env00 <- new.env()
.env00$flag <- FALSE
dfs <- function(ll){
if(!is.call(ll)) {
if (length(ll) && as.character(ll) == "x") .env00$flag <- TRUE
return()
} else {
for(ii in 1:length(ll)) dfs(ll[[ii]])
}
}
pt <- as.list(parse(text=txt))
dfs(pt[[1]])
return(.env00$flag)
}
# Remove leading and trailing white space from text and replace all blanks with "NA"
StripBlanks <- function(x){
x <- sub("^[[:space:]]*(.*?)[[:space:]]*$", "\\1", x, perl=TRUE)
x[x=="NA"|nchar(x) == 0] <- NA
return(x)
}
# Keep all our coercions in here
# to.levels means you're turning a factor into its levels, rather than
# as.integer()
# Coercing a factor to a factor will keep its level ordering.
myDataTypeCoercions <- function(typ, x, to.levels=FALSE){
rv <- NA
tryCatch(
if(to.levels){
stopifnot(is.factor(x))
rv <- levels(x)[as.integer(x)]
rv <- myDataTypeCoercions("character", rv)
} else if(typ=="integer"){
rv <- as.integer(x)
} else if (typ== "logical"){
rv <- as.logical(x)
} else if (typ=="numeric") {
rv <- as.numeric(x)
} else if ("factor"%in%typ){
sbx <- StripBlanks(x)
if(inherits(x, "factor")){
theLevels = unique(StripBlanks(levels(x)))
} else {
theLevels = unique(sbx, na.last = TRUE)
}
rv <- factor(sbx, levels=theLevels)
} else {
x <- as.character(x)
x[is.na(x)] <- ""
rv <- sub("^[[:space:]]*(.*?)[[:space:]]*$", "\\1", x, perl=TRUE)
rv[nchar(rv) == 0] <- ""
}, warning = function(w) return(rv))
return(rv)
}
GetClasses <- function(x){
nc <- ncol(x)
rv <- rep(NA, nc)
for(jj in 1:nc){
cc <- x[,jj]
cx <- class(cc)
# class might be "ordered factor"
if("factor"%in%cx) {
cx <- "factor"
} else if(!is.atomic(cc) || length(cx) != 1) {
cx <- "character"
}
rv[jj] <- cx
}
return(rv)
}
# Coerce frame2 to theClasses1 column classes
CoerceDataTypes <- function(frame2, theClasses1, to.levels=FALSE){
stopifnot(is.data.frame(frame2))
theClasses2 <- GetClasses(frame2)
if(NA%in%theClasses1) stop("Trying to coerce to NA class")
stopifnot(length(theClasses1) == length(theClasses2))
xx <- theClasses1 != theClasses2
xx[is.na(xx)] <- TRUE
for(jj in which(xx)){
frame2[,jj] <- myDataTypeCoercions(theClasses1[jj], frame2[,jj], to.levels)
} # for jj
return(frame2)
}
# We normally coerce when we change cells, but sometimes we might want to paste
# and accept the default data frame coercion
ChangeCells <- function(df, nf, row.idx, col.idx, do.coercion=T){
dmm <- dim(df)
if(!is.data.frame(nf)){
nf <- data.frame(nf, check.names=FALSE)
}
if(missing(row.idx)) row.idx <- 1:dmm[1]
if(missing(col.idx)) col.idx <- 1:dmm[2]
stopifnot(ncol(nf) == length(col.idx) && nrow(nf) == length(row.idx))
oldf = df[row.idx, col.idx, drop=F]
theClasses <- GetClasses(df)
newClasses <- GetClasses(nf)
if(do.coercion){
nf <- CoerceDataTypes(nf, theClasses[col.idx])
}
idxf <- which(theClasses == "factor")
idxf <- idxf[idxf%in%col.idx]
if(length(idxf)){
for(jj in idxf){
nf.col = which(col.idx==jj)
xx <- df[[jj]]
lvls <- levels(xx)
to.model <- as(nf[,nf.col], class(lvls))
to.model[!nchar(to.model)] <- NA # 3-16-10
# we're changing the levels
if((!all(to.model%in%lvls))||(!all(xx[row.idx]%in%xx[-row.idx]))){
x <- as.vector(xx)
x[row.idx] <- to.model
df[,jj] <- myDataTypeCoercions("factor", x)
} else {
df[row.idx,jj] <- to.model
}
}
cc <- !col.idx%in%idxf
df[row.idx, col.idx[cc] ] <- nf[,cc,drop=F]
} else { # no factors to change
if(!do.coercion){
# if we paste into a blank or NA column,
# want to make sure the new frame takes attributes
for(jj in seq(length=length(newClasses))){
df.jj = col.idx[jj]
if (newClasses[jj] != theClasses[df.jj] && all(df[,df.jj] == ""))
class(df[,df.jj]) <- newClasses[jj]
}
}
df[row.idx, col.idx] <- nf
}
return(list(df = df,
undo = list(
func = "ChangeCells",
args = list(nf=oldf, row.idx=row.idx, col.idx=col.idx, do.coercion=T)
)
))
}
SetFactorAttributes <- function(df, col.idx, info){
idx <- col.idx
theCol <- df[[idx]]
stopifnot("factor"%in%class(theCol))
lvls <- levels(theCol)
old <- list(levels=lvls)
if(length(lvls) > 1){
old$contrasts <- contrasts(theCol)
old$contrast.name <- attr(theCol, "contrast.name")
}
if(identical(info$ordered, TRUE))
theCol <- as.ordered(theCol)
if(!is.null(info$levels))
theCol <- factor(theCol, levels=info$levels)
if(!is.null(info$contrasts))
contrasts(theCol) <- info$contrasts
if(!is.null(info$contrast.name))
attr(theCol, "contrast.name") <- info$contrast.name
df[[idx]] <- theCol
return(list(df = df,
undo = list(
func = "SetFactorAttributes",
args = list(col.idx=idx, info=old)
)
))
}
# to.levels: when coercing a factor, use levels(x)[as.numeric(x)]
CoerceColumns <- function(df, theClasses, col.idx, to.levels=FALSE){
idx <- col.idx
if(length(theClasses) == 1 && length(idx) > 1)
theClasses <- rep(theClasses, length(idx))
stopifnot(length(idx) == length(theClasses))
stopifnot(max(idx) <= ncol(df))
old.c <- GetClasses(df)[idx] # the previous class of the column
df[,idx] <- CoerceDataTypes(df[,idx,drop=F], theClasses, to.levels)
return(list(df = df,
undo=list(
func = "CoerceColumns",
args = list(theClasses = old.c, col.idx=idx)
)
))
}
ChangeColumnNames <- function(df, theNames, col.idx){
idx <- col.idx
stopifnot(length(idx) == length(theNames))
stopifnot(max(idx) <= ncol(df))
oldNames <- colnames(df)[idx]
colnames(df)[idx] <- theNames
return(list(df = df,
undo=list(
func = "ChangeColumnNames",
args = list(theNames = oldNames, col.idx=idx)
)
))
}
ChangeRowNames <- function(df, theNames, row.idx){
idx <- row.idx
stopifnot(length(idx) == length(theNames))
stopifnot(max(idx) <= nrow(df))
rdf <- rownames(df)
oldNames <- rdf[idx]
theNames <- make.unique(c(rdf[-idx], theNames))[length(rdf)+1:length(idx)-length(idx)]
rownames(df)[idx] <- theNames
df[idx, 1] <- theNames
return(list(df = df,
undo=list(
func = "ChangeRowNames",
args = list(theNames = oldNames, row.idx=idx)
)
))
}
# Makes indexes
InsertIndex <- function(orig, insertions){
all.idx <- seq(length=orig)
li <- seq(length=length(insertions))
del.idx <- insertions + li - 1
ins.idx <- orig + li
for(jj in li){
after.idx = max(del.idx[jj]-1, 0)
all.idx <- append(all.idx, ins.idx[jj], after=after.idx)
}
return(all.idx)
}
# deleting indexed rows and columns
DeleteRows <- function(df, row.idx){
idx <- row.idx
new_df <- df[-idx,,drop=F]
new_df = maintain_null_rownames(df, new_df)
list(df = new_df,
undo = list(
func = "InsertRows",
args = list(
nf = df[idx,,drop=F],
row.idx=idx-1:length(idx)+1)))
}
InsertRows <- function(df, nf, row.idx){
idx <- row.idx
ddf <- dim(df)
stopifnot(ddf[2] == dim(nf)[2] && dim(nf)[1] == length(idx))
rv <- InsertNARows(df, row.idx)
new_df <- rv$df
nf <- CoerceDataTypes(nf, GetClasses(df))
new_df[row.idx,] <- nf
new_df = maintain_null_rownames(df, new_df)
rv$df <- new_df
return(rv)
}
RowNamesAreNull = function(df)
identical(rownames(df), as.character(1:dim(df)[1]))
maintain_null_rownames = function(df, new_df){
# set this flag to TRUE if the row names are 1:dim(df)[1]
# and remember that the last rowname is actually " ", so omit it
if(EXTRA_ROW) {
row_names_null_flag <- identical(rev(rownames(df))[-1], as.character((dim(df)[1]-1):1))
} else {
row_names_null_flag <- RowNamesAreNull(df)
}
# insert the new frame into the old frame at the insertion position
if(row_names_null_flag && dim(df)[1]>0){
#cat("row names are null\n")
rownames(new_df) <- new_df[,1] <- seq(length=nrow(new_df))
}
return(new_df)
}
InsertNARows <- function(df, row.idx){
idx <- row.idx
ddf <- dim(df)
xx <- InsertIndex(ddf[1], idx)
lidx <- length(idx)
nf <- data.frame(rbind(rep(NA, ddf[2])))[rep(1, lidx),]
colnames(nf) <- colnames(df)
rownames(nf) <- make.unique(c(rownames(df), idx+1:lidx-1))[ddf[1]+1:lidx]
nf[,ddf[2]] <- ""
nf[,1] <- rownames(nf)
nf <- CoerceDataTypes(nf, GetClasses(df))
new_df = rbind(df, nf)[xx,,drop=F]
new_df = maintain_null_rownames(df, new_df)
list(df = new_df,
undo = list(
func = "DeleteRows",
args = list(
row.idx=idx+1:length(idx)-1)))
}
# Check if we have null type column names
ColNamesAreNull = function(df)
identical(
colnames(df)[seq(from=2, length.out=(ncol(df)-2))],
DEFAULT_COLNAMES[seq(from=1, length.out=(ncol(df)-2))]
)
# if the old df (df) has default colnames, make sure the new_df does too
maintain_null_colnames = function(df, new_df){
stopifnot(ncol(df)>=2)
col_names_null_flag <- ColNamesAreNull(df)
if(col_names_null_flag && ncol(new_df) > 2)
colnames(new_df) <- c("rows", DEFAULT_COLNAMES[seq(from=1, length.out=(ncol(new_df)-2))], "")
return(new_df)
}
DeleteColumns <- function(df, col.idx) {
idx <- col.idx
new_df <- df[,-idx,drop=F]
new_df = maintain_null_colnames(df, new_df)
list(df = new_df,
undo = list(
func = "InsertColumns",
args = list(
nf = df[,idx,drop=F],
col.idx=idx-1:length(idx)+1)))
}
InsertColumns <- function(df, nf, col.idx){
idx <- col.idx
ddf <- dim(df)
stopifnot(ddf[1] == dim(nf)[1] && dim(nf)[2] == length(idx))
xx <- InsertIndex(ddf[2], idx)
new_df = cbind(df, nf)[,xx,drop=F]
new_df = maintain_null_rownames(df, new_df)
list(df = new_df,
undo = list(
func = "DeleteColumns",
args = list(
col.idx=idx+1:length(idx)-1)))
}
InsertNAColumns <- function(df, col.idx, NA.opt=""){
idx <- col.idx
ddf <- dim(df)
xx <- InsertIndex(ddf[2], idx)
lidx <- length(idx)
nf <- data.frame(X=cbind(rep(NA.opt, ddf[1])),stringsAsFactors=FALSE, check.names=FALSE)[,rep(1, length(idx)),drop=F]
colnames(nf) <- make.unique(c(colnames(df), DEFAULT_COLNAMES[idx+1:lidx-2]))[ddf[2]+1:lidx]
new_df <- cbind(df, nf)[,xx,drop=F]
new_df = maintain_null_colnames(df, new_df)
list(df = new_df,
undo = list(
func = "DeleteColumns",
args = list(
col.idx=idx+1:length(idx)-1)))
}
# insert new frame dat
GetTaskPasteIn <- function(theFrame, dat, insert.row, insert.col,
do.rownames = F, do.colnames = F, do.coercion=T){
stopifnot(class(dat) == "data.frame")
theRownames <- NULL
theColnames <- NULL
if(do.rownames)
theRownames <- rownames(dat)
if (do.colnames)
theColnames <- colnames(dat)
dd <- dim(dat)
dm <- dim(theFrame)
if (is.null(insert.row)) insert.row <- 1
if (is.null(insert.col)) insert.col <- 1
ins <- c(insert.row, insert.col)
ins.end <- c(ins[1]+dd[1]-1, ins[2]+dd[2]-1) # end of insertion range
tasks <- list()
# need to expand the frame by adding NA rows/cols
if(dm[1] < ins[1]+dd[1]-1){ # Rows
#Indexing: 1 means insert so it forms new row 1, so there are N+1 insert
# positions with an N row data frame.
#idx <- (dm[1]+1):(ins[1]+dd[1])
#idx <- rep(dm[1], length(idx))
idx <- rep(dm[1]+1, ins[1]+dd[1]-dm[1]-1+EXTRA_ROW)
tasks[[length(tasks)+1]] <- list(func="InsertNARows", args=list(row.idx=idx))
}
if(dm[2] < ins[2]+dd[2]){ # Columns
#idx <- (dm[2]+1):(ins[2]+dd[2])
idx <- rep(dm[2], dd[2]+ins[2]-dm[2])
#idx <- rep(dm[2], length(idx))
tasks[[length(tasks)+1]] <- list(func="InsertNAColumns", args=list(col.idx=idx))
}
row.idx <- ins[1]:ins.end[1]
col.idx <- ins[2]:ins.end[2]
if(do.rownames && !is.null(theRownames))
tasks[[length(tasks)+1]] <- list(func="ChangeRowNames",
arg = list(theNames = theRownames, row.idx=row.idx))
if(do.colnames && !is.null(theColnames))
tasks[[length(tasks)+1]] <- list(func="ChangeColumnNames",
arg = list(theNames = theColnames, col.idx=col.idx))
if(nrow(dat) && ncol(dat))
tasks[[length(tasks)+1]] <- list(func="ChangeCells",
args=list(nf=dat, row.idx=row.idx, col.idx=col.idx, do.coercion=do.coercion))
return(tasks)
}
# Do a user call
DoUserCall <- function(call_name, fargs, .local){
handler <- .local$changed.handler[[call_name]]
if(!is.null(handler$func) && is.function(handler$func) ){
fargs <- append(list(obj = .local$group.main), fargs)
if(!is.null(handler$data))
fargs <- append(fargs, list(data=handler$data))
tryCatch({
do.call(handler$func, fargs)
}, error = function(e) warning(e))
}
}
# Do a task list
# returns list with df=new frame, undo = list
DoTask <- function(.local, df, task, handler=NULL){
#print("DoTask called")
w <- TransientWindow("Updating...", .local)
on.exit(w$destroy())
undo <- list()
for(taskItem in task){
arg <- taskItem$arg
func.name <- taskItem$func
arg$df <- df
# This can fail when it's too big
#print(arg)
rv <- do.call(taskItem$func, arg)
df <- rv$df
undo[[length(undo)+1]] <- rv$undo
if("col.idx"%in%names(taskItem$arg)) taskItem$arg$col.idx <- ToExternalColIdx(taskItem$arg$col.idx)
DoUserCall(func.name, taskItem$arg, .local)
if("col.idx"%in%names(taskItem$arg)) taskItem$arg$col.idx <- ToInternalColIdx(taskItem$arg$col.idx)
}
return(list(df=df, undo=undo))
}
bgColor <- list(as.GdkColor(c(237,236,235)*256),as.GdkColor(c(235,234,219)*256))[[(PLATFORM_OS_TYPE == "windows")+1]]
selectedColor <- as.GdkColor(c(198, 213, 253)*256) # Linux
whiteColor <- as.GdkColor(c(255, 255, 255)*256)
#selectedColor <- as.GdkColor(c(255,255,255)*256) # Linux
selectedColumnColor <- as.GdkColor(c(198, 213, 253)*256) # Linux
selectedTextColor <- as.GdkColor("black")
myValidInputKeys <- c(GDK_space:GDK_asciitilde, GDK_Delete, GDK_BackSpace)
myMetaKeys <- c(GDK_Shift_L, GDK_Shift_R, GDK_Control_L, GDK_Control_R)
myShiftKeys <- c(GDK_Shift_L, GDK_Shift_R)
myValidNavigationKeys <- c(GDK_Down, GDK_Left, GDK_Up, GDK_Right,
GDK_Page_Up, GDK_Page_Down, GDK_Return, GDK_ISO_Left_Tab, GDK_Tab, GDK_Home, GDK_End, GDK_KP_Enter, GDK_KP_Left:GDK_KP_Down)
myValidKeys <- c(myValidInputKeys, myMetaKeys, myValidNavigationKeys, GDK_Insert)
# make our own key-letter list
myGDKKeys <- sapply(apropos("GDK_"), get)
myGDKKeys <- sort(unlist(myGDKKeys[sapply(myGDKKeys, class)=='numeric']))
exclude.factor <- NA
#' Convenience function to call data frame editor in its own window
#'
#' @param items A data frame (?) to display graphically
#' @param dataset.name Name for data set
#' @param size (height, width)
#' @param col.width (width)
#' @return An object of class GtkDfEdit for which a few RGtk2-style methods are defined
#' @export
# Edit object in place argument
dfedit <- function(items, dataset.name = deparse(substitute(items)),
size=c(600, 300), col.width = 64, editable=TRUE,
autosize = is.null(dim(items))||ncol(items)<25,
update=TRUE, modal=TRUE){
stopifnot(modal%in%c(TRUE, FALSE))
if(missing(items)||is.null(items)) {
items <- data.frame(NULL)
#items <- data.frame(matrix("", 1E2, 26), stringsAsFactors=FALSE, check.names=FALSE)
dataset.name <- "Untitled"
}
obj <- gtkDfEdit(items, dataset.name, size.request=size, col.width = col.width, editable=editable, autosize=autosize, update=update)
#dialog <- gtkDialog(dataset.name, NULL, "modal", "Close", 1,show = FALSE)
if(identical(editable, TRUE) && identical(modal, TRUE)) dialog <- gtkDialog("Data Frame Editor", NULL, c("destroy-with-parent"), "gtk-ok", 1, "gtk-cancel", 0, show = FALSE)
else dialog <- gtkDialog("Data Frame Viewer", NULL, c("destroy-with-parent"), "gtk-close", 0, show = FALSE)
#dialog$setModal(FALSE)
#dialog$setPosition(GtkWindowPosition["center-on-parent"])
infoBox <- gtkHBoxNew()
selLabel = gtkLabelNew("")
selLabel$setAlignment(0, 0.5)
selLabel$setSizeRequest(150, -1)
infoBox$packStart(selLabel, padding=2, expand=FALSE)
.local <- obj$getData(".local")
dialog[["vbox"]]$packStart(infoBox, padding=2, expand=FALSE)
dialog[["vbox"]]$add(obj)
dialog[["vbox"]]$setFocusChild(obj)
obj$setActionHandler("OnLoad", function(obj, sourc, typ) {
.local <- obj$getData(".local")
dialog$setTitle(paste(.local$dataset.name, "- dfedit", VERSION_STRING))
})
obj$setActionHandler("Selection", function(obj, selections, ...) {
selLabel$setText("")
dstr <- ""
if(length(selections)==1){
sel.col <- c(selections[[1]]$start['col.idx'], selections[[1]]$end['col.idx'])
sel.col.let <- DEFAULT_COLNAMES[c(selections[[1]]$start['col.idx'], selections[[1]]$end['col.idx'])]
sel.row <- c(selections[[1]]$start['row.idx'], selections[[1]]$end['row.idx'])
if(length(sel.row)==2 && length(sel.col)==2){
sel.col <- sort(sel.col)
sel.row <- sort(sel.row)
if(sel.row[1]==sel.row[2] && sel.col[1]==sel.col[2]){
range.txt <- paste(sel.col.let[1], sel.row[2], sep="")
dstr <- as.character(.local$theFrame[sel.row[1], sel.col[1]+COLUMN_OFFSET])
} else {
range.txt <-
paste(sel.row[2]-sel.row[1]+1,
" R x ", sel.col[2] - sel.col[1]+1, " C", sep="")
# paste(sel.col.let[1], sel.row[1],
# ":", sel.col.let[2], sel.row[2], sep="")
}
}
} else if(length(selections)>1){
range.txt <- "Multiple ranges"
} else {
range.txt <- ""
}
selLabel$setText(range.txt)
})
view <- .local$view
#dialog$showAll()
#win <- gtkWindowNew()
#win$setTitle(dataset.name)
if(modal){
dialog$showAll()
if (dialog$run() == 1){
rv <- invisible(obj$getDataFrame())
dialog$destroy()
return(invisible(rv))
} else {
dialog$destroy()
return(invisible(items))
}
} else {
gSignalConnect(dialog, "response", gtkWidgetDestroy)
dialog$showAll()
return(invisible(obj))
}
}
dfview <- function(items, dataset.name = deparse(substitute(items)),
size=c(600, 300), col.width = 64, editable=FALSE,
autosize = is.null(dim(items))||ncol(items)<25,
update=FALSE){
rv <- dfedit(items, dataset.name,
size=size, col.width=col.width, editable=editable, autosize=autosize, update=update)
invisible(rv)
}
# Our handler:
# isText <- theClass == "factor" || theClass == "character"
# renderer <- .local$allColumns[[kk-1]]$renderer
# renderer.set <- renderer['ellipsize-set']
# if( isText && !renderer.set) {
# renderer['ellipsize'] <- PangoEllipsizeMode['end']
# renderer['ellipsize-set'] <- TRUE
# } else if (!isText && renderer.set) {
# renderer['ellipsize'] <- PangoEllipsizeMode['none']
# renderer['ellipsize-set'] <- FALSE
# }
DoUndo <- function(.local){
if(!length(.local$undoStack)) return(TRUE)
undo <- .local$undoStack[[length(.local$undoStack)]]
rv <- DoTask(.local, .local$theFrame, rev(undo), .local$changed.handler)
UpdateDfEditor(.local, rv$df)
if(length(.local$undoStack)){
#.local$redoStack[[length(.local$redoStack)+1]] <- rev(undo)
.local$undoStack[[length(.local$undoStack)]] <- NULL
}
}
DoRedo <- function(.local){
if(!length(.local$redoStack)) return(TRUE)
redo <- .local$redoStack[[length(.local$redoStack)]]
rv <- DoTask(.local, .local$theFrame, redo, .local$changed.handler)
UpdateDfEditor(.local, rv$df)
if(length(.local$undoStack))
.local$undoStack[[length(.local$undoStack)]] <- NULL
}
TransientWindow <- function(txt, .local=NULL){
w <- gtkWindowNew("", show=F) #GTK_WINDOW_POPUP
gtkWidgetSetCanFocus(w, TRUE)
gtkWindowSetSkipTaskbarHint(w, TRUE)
gtkWidgetSetSizeRequest(w, 100, 30)
gtkWindowSetDecorated(w, FALSE)
lt <- gtkLabelNew(txt)
gtkAdd(w, lt)
gtkWindowSetPosition(w, GtkWindowPosition["center-on-parent"])
gtkWindowSetTransientFor(w, .local$toplevel)
gtkWindowSetModal(w, TRUE)
gtkWidgetShowAll(w)
#gtkWidgetGrabFocus(w)
return(w)
}
# restrict means you can't select the last index
GetSelectedRows <- function(tv, .local, ignore.last.row=FALSE){
# block transient interactions with a popup
# w <- TransientWindow("Getting row selection...", .local)
# on.exit(w$destroy())
sr <- integer(0)
#tryCatch({
selection <- gtkTreeViewGetSelection(tv)
rv <- gtkTreeSelectionGetSelectedRows(selection)$retval
lrv <- length(rv)
if(lrv){
#sr <- vector("integer", lrv)
#for(ii in seq(length=lrv))
# sr[ii] <- gtkTreePathGetIndices(rv[[ii]])
sr <- sapply(rv, gtkTreePathGetIndices)
if(!ignore.last.row){
if(is.numeric(sr)) {
sr <- sr + 1
} else {
sr <- integer(0)
}
} else { # remove last row
if(is.numeric(sr)) {
sr <- sr + 1
sr <- sr[!sr%in%dim(.local$theFrame)[1]]
} else {
sr <- integer(0)
}
}
}
#},
#error=function(e) {
# warning(e)
# integer(0)
#})
return(sr)
}
# Make a data frame to put into the model.
# We have an extra row for names and a blank at the end.
# Also, data frames with all-NAs turn into logical, which displays badly.
MakeInternalDataFrame <- function(dataset, add.rows=EXTRA_ROW, add.columns=T, NA_replace = NA_character_){
if(is.null(dim(dataset))) dataset <- cbind(dataset)
if(!length(rownames(dataset)) && dim(dataset)[1])
rownames(dataset) <- seq(length=dim(dataset)[1])
if(!length(colnames(dataset)) && dim(dataset)[2])
colnames(dataset) <- DEFAULT_COLNAMES[seq(length=dim(dataset)[2])]
# Fix bug with NA row name, 09-15-2010
row_names_dataset <- row.names(dataset)
row.problem.idx <- is.na(row_names_dataset) | duplicated(row_names_dataset)
if(any(row.problem.idx)){
dataset <- dataset[!row.problem.idx,,drop=F]
message(paste("Removed missing or duplicated row names at positions:", paste(which(row.problem.idx), collapse=", ")))
}
if(add.columns){
theFrame <- data.frame(rows = row.names(dataset), dataset,
" " = vector("character", nrow(dataset)),
check.names = FALSE, stringsAsFactors = FALSE)
} else {
theFrame <- data.frame(dataset, stringsAsFactors=FALSE)
}
# turn all-NA columns to whatever default we want
# if(dim(theFrame)[2] > 0) {
# which.allNA <- which(colSums(is.na(theFrame)) == dim(theFrame)[1])
# for(jj in which.allNA) theFrame[,jj] <- NA_replace
# }
#
theClasses <- GetClasses(theFrame)
for(jj in which(theClasses == "character"))
theFrame[,jj] <- myDataTypeCoercions("character", theFrame[,jj])
if(add.rows){
if (add.columns){
blankRow <- data.frame(rows=" ", rbind(rep(NA, dim(dataset)[2])), " " = "", row.names = " ", stringsAsFactors=F)
} else {
blankRow <- data.frame(rbind(rep(NA, dim(dataset)[2])), stringsAsFactors=F)
}
blankRow <- CoerceDataTypes(blankRow, theClasses)
for(jj in which(theClasses == "factor")){
theFrame[,jj] <- myDataTypeCoercions("factor", theFrame[,jj])
blankRow <- SetFactorAttributes(df=blankRow, col.idx=jj,
info=list(levels=levels(theFrame[,jj]), ordered=is.ordered(theFrame[,jj])))$df
}
names(blankRow) <- names(theFrame)
theFrame <- rbind(theFrame, blankRow)
}
return(theFrame)
}
# Make a data frame to extract from the model.
MakeExternalDataFrame <- function(theFrame, .local=NULL){
if(EXTRA_ROW) new.frame <- theFrame[-nrow(theFrame),-c(1, ncol(theFrame)), drop=F]
else
new.frame <- theFrame[,-c(1, ncol(theFrame)), drop=F]
if(!is.null(.local)){
dataset.class <- .local$dataset.class
tryCatch({
if(length(dataset.class)==0 || "data.frame"%in%dataset.class){
class(new.frame) <- dataset.class
} else if(length(dataset.class)==1) {
new.frame <- as(new.frame, dataset.class)
}}, error = function(e) warning(paste("Couldn't create data of class", paste(dataset.class, collapse = ", "))))
# Copy any other attributes over
dataset.attributes <- .local$dataset.attributes
copy.attr <- dataset.attributes[!names(dataset.attributes)%in%c('class', 'dim', 'dimnames', 'names', 'row.names')]
if(length(copy.attr)) for(ii in 1:length(copy.attr)) attr(new.frame, names(copy.attr)[ii]) <- copy.attr[[ii]]
}
return(new.frame)
}
# return last usable row path
MakeLastPath <- function(df){
if(!EXTRA_ROW){
if(dim(df)[1] == 0) return(gtkTreePathNewFromString("0"))
} else { # adding an extra row
if(dim(df)[1] == 1) return(gtkTreePathNewFromString("0"))
}
return(gtkTreePathNewFromString(as.character(dim(df)[1]-1-EXTRA_ROW)))
}
quick_entry <- function(msg, handler, data=NULL, win=NULL) {
dialog <- gtkDialog(msg, NULL, c("destroy-with-parent"), "gtk-ok", 1, "gtk-cancel", 0, show = F)
#dialog$setDecorated(FALSE)
entry = gtkEntryNew()
entry$setSizeRequest(300, -1)
gSignalConnect(entry, "key-press-event", function(obj, evt){
if(evt[["keyval"]] == GDK_Return) {
handler(entry$getText(), data)
dialog$destroy()
}
if(evt[["keyval"]] == GDK_Escape) {
dialog$destroy()
}
return(FALSE)
})
if(!is.null(win)) {
checkPtrType(win, "GtkWindow")
dialog$setPosition(GtkWindowPosition["center-on-parent"])
dialog$setTransientFor(win)
}
gSignalConnect(dialog, "response", function(dlg, arg1, user.data) {
if(arg1==1)
handler(entry$getText(), user.data)
dialog$destroy()
}, data=data)
dialog[["vbox"]]$packStart(entry, TRUE, TRUE, 10)
dialog$showAll()
}
quick_query <- function(message, handler, data, win=NULL) {
dialog <- gtkDialog("Query", NULL, c("modal", "destroy-with-parent"), "gtk-ok", 1, "gtk-cancel", 0, show = F)
label <- gtkLabel(message)
if(!is.null(win)) {
checkPtrType(win, "GtkWindow")
dialog$setPosition(GtkWindowPosition["center-on-parent"])
dialog$setTransientFor(win)
}
gSignalConnect(dialog, "response", function(dlg, arg1, user.data) {
if(arg1) handler(data)
dialog$destroy()
})
dialog[["vbox"]]$packStart(label, TRUE, TRUE, 10)
dialog$showAll()
}
quick_message <- function(message, caption="Warning", win=NULL) {
dialog <- gtkDialog("Message", NULL, c("modal","destroy-with-parent"), "gtk-ok", 1,
show = FALSE)
label <- gtkLabel(message)
if(!is.null(win)) {
checkPtrType(win, "GtkWindow")
dialog$setPosition(GtkWindowPosition["center-on-parent"])
dialog$setTransientFor(win)
}
gSignalConnect(dialog, "response", gtkWidgetDestroy)
dialog[["vbox"]]$packStart(label, TRUE, TRUE, 10)
dialog$showAll()
}
#warning <- quick_message
IsCtrl <- function(stat) (as.flag(stat) & GdkModifierType['control-mask'])
IsShift <- function(stat) (as.flag(stat) & GdkModifierType['shift-mask'])
IsCtrlShift <- function(stat) (as.flag(stat) & GdkModifierType['control-mask'] & GdkModifierType['shift-mask'])
CtrlLetter <- function(keyval, stat, let)
keyval == let && (as.flag(stat) & GdkModifierType['control-mask'])
ShiftLetter <- function(keyval, stat, let)
keyval == let && (as.flag(stat) & GdkModifierType['shift-mask'])
CtrlShiftLetter <- function(keyval, stat, let)
keyval == let && (as.flag(stat) & GdkModifierType['control-mask'] & GdkModifierType['shift-mask'])
# this is all horribly platform dependent
CopyToClipboard <- function(dat, do.rownames=F, do.colnames=F){
write.function <- function(dat, p)
write.table(dat, p, row.names=F, col.names=F, quote=F, sep="\t")
if(do.rownames) {
t.dat <- t(cbind(rownames(dat), dat))
} else {
t.dat <- t(dat)
}
dat2 <- paste( apply(t.dat, 2, function(ll) {
ll[is.na(ll)] <- ""
paste(ll, collapse="\t")
}), collapse=NEWLINE_CHAR)
if(do.colnames) {
dat.cn <- ""
if(do.rownames) dat.cn <- "\t"
dat.cn <- paste(dat.cn, paste(colnames(dat), collapse="\t"), sep="")
dat2 <- paste(dat.cn, dat2, sep=NEWLINE_CHAR)
}
if(.Platform$OS.type == "windows") {
#write.function(dat, "clipboard")
get("writeClipboard", envir=.GlobalEnv)(dat2)
} else if (.Platform$OS.type == "unix"){
if (Sys.info()["sysname"] == "Darwin")
a <- pipe("pbcopy", "w")
else {
if(!length(system('which xclip', intern=T))){
quick_message("xclip must be installed to copy")
return(FALSE)
}
a <- pipe("xclip -selection c", open="w")
}
}
if(.Platform$OS.type != "windows" )
tryCatch(
write.function(dat2, a),
error = stop,
finally = close(a)
)
}
# Where to read from
GetPasteClipboardPipe <- function(){
if(PLATFORM_OS_TYPE == "windows") {
p <- "clipboard"
} else if (PLATFORM_OS_TYPE == "unix"){
if(!length(system('which xsel', intern=T))){
quick_message("xsel must be installed to paste")
return(FALSE)
}
p <- pipe("xsel -o -b", open="r")
} else if (PLATFORM_OS_TYPE == "mac"){
p <- pipe("pbpaste")
} else{
stop("Unrecognized platform type")
}
}
CloseClipboardPipe <- function(p)
if (PLATFORM_OS_TYPE == "unix" || PLATFORM_OS_TYPE=="mac" )
close(p)
# Dialog for reading in tables
#
# Options dialog menu
# setwd("~/rgtk2extras/pkg/RGtk2Extras/R/")
# This wraps read.table and tries to handle a few common errors
# First common problem. Bug in read.table: if nrows <= 5 it gives an error
# So if it throws an error, just try scanning 1 row at a time.
# Error handling: first 5 rows can lack EOF and give an error
# Second common problem: numbers with commas or full stop delimiters.
# Use 'num.string.ignore' argument to filter out "%" "," etc
# quote="", na,strings="", comment.char=""
# Get pasted data from clipboard.
# Arguments are for read.table
ReadTableFromClipboard = function(...){
CHAR_MAX = 10000 # max chars in first 5 lines if they fail
rv <- tryCatch({
b = GetPasteClipboardPipe()
rv = read.table(b, ...)
},
error = function(e) {
# if the read table fails,
# we might need to append EOF
if(identical(as.numeric(gregexpr("incomplete final line", e$message)), 1)){
tryCatch({
# Do this by calling pushBack on another paste pipe
b2 = GetPasteClipboardPipe()
#print(readLines(b2))
b2_lines = readChar(b2, CHAR_MAX)
# tfn = tempfile(fileext=".txt")
# cat(c(b2_lines, "\n"), file=tfn)
#print(readLines(tfn))
# rt2 = read.table(tfn, ...)
# unlink(tfn)
#print(b2_lines)
if(nchar(b2_lines)==CHAR_MAX)
warning(paste("Maximum of",
CHAR_MAX, "characters in first 5 lines exceeded."))
pushBack(c(b2_lines, ""), b2)
rv = read.table(b2, ...)
}, error = stop,
finally = CloseClipboardPipe(b2))
} else {
# something else went wrong
stop(e$message)
}
},
finally= CloseClipboardPipe(b))
return(rv)
}
CheckStringsForNumeric = function(rv, num.string.ignore = "%"){
# Go through columns. If the first 5 lines give intelligible numbers on removing
# the ignored characters, read as numbers.
suppressWarnings({
if(!is.null(num.string.ignore) && nrow(rv)) {
num.string.ignore = paste("[", num.string.ignore, "]", sep="")
test.idx = 1:min(5, nrow(rv))
for(jj in seq(length=ncol(rv))){
first.entries = rv[test.idx,jj]
where.na = is.na(first.entries)
# if removing the separator/% char doesn't introduce new NA's and is OK
if(!all(where.na) && is.character(first.entries[!where.na])){
read.nums = as.numeric(gsub(num.string.ignore, "", first.entries))
if(any(is.numeric(read.nums)) && sum(is.na(read.nums)) <= sum(where.na)){
rv[,jj] <- as.numeric(gsub(num.string.ignore, "", rv[,jj]))
}
}
}
}
})
#gsub(",", "", "1,234,567", fixed = TRUE)
return(rv)
}
# Returns true only if the argument is 1x1, and
# its whitespace-trimmed content doesn't contain the delimiter
LooksLikeASingleItem = function(dat,
delimit=paste(" \t;", ifelse(identical(options("OutDec")[[1]], "."), ",", ""), sep="")
){
dat[1,1] <- StripBlanks(dat[1,1])
!is.null(dat) && identical(as.numeric(dim(dat)), c(1,1)) &&
!length(grep(paste("[", delimit, "]", sep=""), dat[1,1]))
}
ReadFromClipboardWrapper <- function(.local, ...){
# Check to see if the user's pasting in something
# that looks like a regular old number, otherwise,
# go to the ReadFromClipboard dialog
dat <- NULL
tryCatch({
dat <- ReadTableFromClipboard(nrow=2, stringsAsFactors=FALSE)
if(LooksLikeASingleItem(dat)){
dat <- CheckStringsForNumeric(dat, num.string.ignore="[$%]")
rfc <- list(xx=dat,
other.opts = list(paste.row.labels=F, paste.col.labels=F))
} else {
dat <- NULL
}
}, error = function(e) {})
if(is.null(dat)){
rfc <- ReadFromClipboard(...) # character vector
dat <- rfc$xx
}
if(!is.null(rfc)){
#cmd.list <- rfc$cmd.list
other.opts = rfc$other.opts
row.idx <- GetSelectedRows(.local$view, .local, ignore.last.row=EXTRA_ROW)
sc <- GetSelectedColumns(.local, restrict=FALSE)
# if there are no rows, we want to paste in
if(!length(row.idx)){
row.idx <- 1
}
# if there are no columns, we want to paste in
if(length(sc) == 0 && length(.local$allColumns) == 1)
sc <- 1 #
if(length(sc) && length(row.idx)){
col.idx <- min(sc)
row.idx <- row.idx[1]
task <- GetTaskPasteIn(.local$theFrame, dat, row.idx, col.idx+COLUMN_OFFSET, do.colnames=other.opts$paste.col.labels, do.rownames = other.opts$paste.row.labels, do.coercion=F)
DoTaskWrapper(.local, task)
}
}
}
# ... args are arguments to read.table, which we might override or ignore (currently ignoring)
# returns the data frame and options
ReadFromClipboard <- function(handler=NULL, user.data=NULL, parent.window=NULL, pipe=NULL, fromFile=FALSE, fileName=NULL, sep=NULL, ...){
## read table dialog
if(fromFile)
stopifnot(!is.null(fileName) && !is.na(fileName) && nchar(fileName))# && file.exists(fileName)) # might be URL
function.opts = list(...)
dialog <- gtkDialog("Read Table", NULL, c("destroy-with-parent"), "gtk-ok", 1, "gtk-cancel", 0, show = F)
dialog$setPosition(GtkWindowPosition["center-on-parent"])
dialog$setTransientFor(parent.window)
#dialog$setDecorated(FALSE)
box0 <- gtkVBoxNew(FALSE, 5)
if (0){
fr1 <- gtkFrameNew(label="Choose File")
box1 <- gtkHBoxNew(FALSE, 5)
fr1$add(box1)
entry1 <- gtkEntryNew()
entry1$setAlignment(0)
button1 <- gtkButtonNewWithLabel("Select File...")
box1$packStart(entry1, TRUE, TRUE, 0)
box1$packStart(button1, FALSE, FALSE, 0)
nlines1_adj <- gtkAdjustment(5, 1, 1000, 1, 1)
nlines1 <- gtkSpinButton(nlines1_adj, 1.0, 0)
nlines1$setValue(5)
label1 <- gtkLabelNew("Lines To Preview:")
box1$packEnd(nlines1, FALSE, FALSE, 0)
box1$packEnd(label1, FALSE, FALSE, 0)
gSignalConnect(button1, "clicked", data=entry1, function(obj, label){
tryCatch({
f <- my_choose_files(file.path(getwd(), "*.*"), multi=F)
if(nchar(f) > 0){
setwd(dirname(f))
# modify settings
ext <- strsplit(f, "[.]")[[1]]
ext <- ext[length(ext)]
if(ext == "csv"){
sep1$setActive(2)
} else if (ext == "txt") {
sep1$setActive(3)
} else if (ext == "prn") {
sep1$setActive(1)
} else {
sep1$setActive(0)
}
entry1$setText(f)
preview.txt <- paste(readLines(con = f, n = nlines1$getValue()), collapse="\n")
tb$getBuffer()$setText(preview.txt)
}
}, error = function(e){print(e)})
})
box1 <- gtkHBoxNew(FALSE, 5)
fr0 <- gtkFrameNew(label="File Preview")
scroll <- gtkScrolledWindowNew()
scroll$setPolicy(GtkPolicyType["automatic"], GtkPolicyType["automatic"])
tb <- gtkTextViewNew()
tb$setEditable(FALSE)
tb$setSizeRequest(400, 100)
scroll$add(tb)
fr0$add(scroll)
tb$getBuffer()$setText("No File Loaded")
box0$add(fr1)
#box0$add(fr0)
}
fr2 <- gtkFrameNew(label="Table Read Options")
box2 <- gtkVBoxNew(FALSE, 5)
sep.types = list("Tab (\"\t\")" = "\t", "Whitespace" = "", "Space (\" \")" = " ", "Comma (\",\")" = ",", "Semicolon (\";\")" = ";")
sep1 <- gtkComboBoxNewText()
for (ll in names(sep.types)) sep1$appendText(ll)
sep1$setTooltipText("The field separator character. Values on each line of the file are separated by this character. White space is one or more spaces, tabs, newlines or carriage returns.")
# if (identical(.Options$dec[[1]], ".")) sep1$setActive(2)
# else
if(is.null(sep)) {
sep1$setActive(0)
} else {
stopifnot(sep%in%sep.types)
sep1$setActive(which(sep==sep.types)-1)
}
hbox <- gtkHBoxNew(FALSE, 0)
hbox$add(gtkLabelNew("Separator"))
hbox$add(sep1)
# box2$packStart(hbox, FALSE, FALSE, 0)
NUMBER_FORMATS = data.frame(row.names= c("thousands", "decimal"),
"US/UK (12,345.67)" = c(",", "."),
"France (12 345,67)" = c(" ", ","),
"Germany (12.345,67)" = c(".", ","), stringsAsFactors=F, check.names=FALSE
)
dec1 <- gtkComboBoxNewText()
dec1$setTooltipText("The characters used for decimal points and thousands separators")
dec1$setSizeRequest(120, -1)
#for (ll in c(".", ",")) dec1$appendText(ll)
for (ll in colnames(NUMBER_FORMATS)) dec1$appendText(ll)
# Default to France if we aren't in US/UK
dec1$setActive(ifelse(identical(options("OutDec")[[1]], "."), "0", "1"))
# hbox <- gtkHBoxNew(FALSE, 0)
hbox$add(gtkLabelNew("Format"))
hbox$add(dec1)
box2$packStart(hbox, FALSE, FALSE, 0)
opt.box <- gtkHBoxNew(FALSE, 0)
opt.box1 <- gtkVBoxNew(FALSE, 0)
opt.box2 <- gtkVBoxNew(FALSE, 0)
opt.box$add(opt.box1)
#opt.box$add(opt.box2)
box2$add(opt.box)
opt.table <- gtkTableNew(6, 2, homogeneous=FALSE)
opt.table$setRowSpacings(5)
opt.box$packEnd(opt.table, TRUE, TRUE, 5)
row.names0 <- gtkCheckButtonNewWithLabel(label="Use Row Labels")
# row.names0$setActive(TRUE)
opt.box1$packStart(row.names0, FALSE, FALSE, 0)
paste.row.labels <- gtkCheckButtonNewWithLabel(label="Row Labels")
paste.col.labels <- gtkCheckButtonNewWithLabel(label="Column Labels")
pasteoptsbox <- gtkVBoxNew()
pasteoptsbox$add(paste.row.labels)
pasteoptsbox$add(paste.col.labels)
pasteoptsbox$setTooltipText("Whether to paste row and column labels along with the data")
check.names1 <- gtkCheckButtonNewWithLabel(label="Check Names")
check.names1$setTooltipText(" If TRUE then the names of the variables in the data frame are checked to ensure that they are syntactically valid variable names. If necessary they are adjusted (by make.names) so that they are, and also to ensure that there are no duplicates.")
pasteoptsbox$add(check.names1)
fr.pasteopts <- gtkFrameNew(label="Paste Options")
fr.pasteopts$add(pasteoptsbox)
header1 <- gtkCheckButtonNewWithLabel(label="Use Header Row")
header1$setTooltipText("Whether the file contains the names of the variables as its first line.")
header1$setActive(FALSE)
#check.opts = function(theArg, checkButton)
# if(theArg%in%names(function.opts) && function.opts[[theArg]]%in%c(TRUE, FALSE)) gtkToggleButtonSetActive(checkButton, (function.opts[[theArg]]))
#check.opts("header", header1)
opt.box1$packStart(header1, FALSE, FALSE, 0)
# Stolen from Peter Dalgaard
ASCII <- c("Any White Space", sapply(1:255, function(i) parse(text=paste("\"\\", structure(i,class="octmode"), "\"", sep=""))[[1]]))
row.names1 <- gtkSpinButton(gtkAdjustment(1, 1, 1000, 1, 1), 1.0, 0)
rowTooltip <- "Switching this on allows the column of the table giving row names to be specified. If it is off, and the first row contains one fewer field than the number of columns, the first column in the input is used for the row names."
row.names0$setTooltipText(rowTooltip)
row.names1$setTooltipText(rowTooltip)
row.names1$setValue(1)
row.names1$setSensitive(row.names0$getActive())
gSignalConnect(row.names0, "toggled", function(widget){
row.names1$setSensitive(widget$getActive())
})
#hbox <- gtkHBoxNew(FALSE, 0)
label = gtkLabelNew("Use Column")
label$setAlignment(1, 0.5)
opt.table$attach(label, 0, 1, 0, 1, xpad=10)
opt.table$attach(row.names1, 1, 2, 0, 1)
skip1 <- gtkSpinButton(gtkAdjustment(0, 0, 1000, 1, 1), 1.0, 0)
skip1$setValue(0)
skip1$setTooltipText("the number of lines of the data file to skip before beginning to read data.")
label = gtkLabelNew("Rows To Skip")
label$setAlignment(1, 0.5)
opt.table$attach(label, 0, 1, 1, 2, xpad=10)
opt.table$attach(skip1, 1, 2, 1, 2)
colClasses1 <- gtkComboBoxNewText()
for (ll in c("Default", "logical", "integer", "numeric", "complex", "character", "raw", "factor", "Date", "POSIXct")) colClasses1$appendText(ll)
colClasses1$setActive(0)
#hbox <- gtkHBoxNew(FALSE, 0)
label = gtkLabelNew("Column Classes")
label$setAlignment(1, 0.5)
opt.table$attach(label, 0, 1, 2, 3, xpad=10)
opt.table$attach(colClasses1, 1, 2, 2, 3)
na.strings1 <- gtkEntryNew()
na.strings1$setTooltipText("strings which are to be interpreted as NA values. Blank fields are also considered to be missing values.")
na.strings1$setText("NA")
hbox <- gtkHBoxNew(FALSE, 0)
label = gtkLabelNew("NA String")
label$setAlignment(1, 0.5)
opt.table$attach(label, 0, 1, 3, 4, xpad=10)
opt.table$attach(na.strings1, 1, 2, 3, 4)
quote1 <- gtkComboBoxNewText()
for (ll in c("", "'", "\"", "\"'")) quote1$appendText(ll)
quote1$setTooltipText("the set of quoting characters. To disable quoting altogether, use \"\" ")
quote1$setActive(0)
#hbox <- gtkHBoxNew(FALSE, 0)
label = gtkLabelNew("Quotes")
label$setAlignment(1, 0.5)
opt.table$attach(label, 0, 1, 4, 5, xpad=10)
opt.table$attach(quote1, 1, 2, 4, 5)
fill1 <- gtkCheckButtonNewWithLabel(label="Fill Unequal Length Rows")
fill1$setTooltipText("If TRUE then in case the rows have unequal length, blank fields are implicitly added.")
fill1$setActive(TRUE)
opt.box1$packStart(fill1, FALSE, FALSE, 0)
stringsAsFactors1 <- gtkCheckButtonNewWithLabel(label="Read Strings As Factors")
stringsAsFactors1$setTooltipText("Convert character variables to factors.")
stringsAsFactors1$setActive(FALSE)
opt.box1$packStart(stringsAsFactors1, FALSE, FALSE, 0)
num.ignore.string1 <- gtkEntryNew()
num.ignore.string1$setTooltipText("Ignore these characters if they appear inside or around numeric variables. For example, reading '12.3%', '12 345.67' and '$512.00' as numbers should ignore '%', ' ' and '$' respectively.")
num.ignore.string1$setText(paste("%$", NUMBER_FORMATS["thousands", dec1$getActiveText()], sep=""))
hbox <- gtkHBoxNew(FALSE, 0)
label = gtkLabelNew("Numbers Ignore")
label$setAlignment(1, 0.5)
opt.table$attach(label, 0, 1, 5, 6, xpad=10)
opt.table$attach(num.ignore.string1, 1, 2, 5, 6)
opt.box1$add(fr.pasteopts)
fr2$add(box2)
#box0$packStart(fr1, FALSE, FALSE, 0)
#box0$packStart(fr0, FALSE, FALSE, 0)
fr3 <- gtkFrameNew(label="Table Preview")
box3 <- gtkVBoxNew(FALSE, 5)
#preview.button <- gtkButtonNewWithLabel(" Preview Table ")
#hbox <- gtkHBoxNew(FALSE, 0)
#hbox$packEnd(preview.button, FALSE, FALSE, 5)
#box3$packStart(hbox, FALSE, FALSE, 0)
tb2 <- gtkTextViewNew()
tb2$setSizeRequest(400, 200)
tb2$getBuffer()$setText("No Table loaded")
box3$packStart(tb2)
read_table <- function(nrows=-1, doMsg=FALSE){
# if(nchar(entry1$getText()) == 0) stop("No file selected")
row.names.arg <- NULL
if(row.names0$getActive()) row.names.arg <- as.integer(row.names1$getText())
col.classes.arg <- NA
cmd.list = list(header= header1$getActive(),
sep = sep.types[[sep1$getActiveText()]],
skip= as.integer(skip1$getText()),
#row.names = row.names.arg,
nrows = nrows,
colClasses= col.classes.arg,
stringsAsFactors = stringsAsFactors1$getActive(),
fill = fill1$getActive(),
dec = NUMBER_FORMATS["decimal", dec1$getActiveText()],
quote = quote1$getActiveText(),
strip.white=TRUE,
comment.char = "",
check.names=check.names1$getActive()
)
if(fromFile){
stopifnot(!is.null(fileName) && !is.na(fileName) && nchar(fileName))# && file.exists(fileName)) # could be url
cmd.list$file <- fileName
GetTable <- read.table
} else {
GetTable <- ReadTableFromClipboard
}
if(colClasses1$getActiveText() != "Default") {
#if(col.classes.arg != "character" && !is.null(row.names.arg)){
cmd.list$nrows=5
xx <- do.call(GetTable, cmd.list) # read it with row names NULL
NN <- dim(xx)[2]
if(NN){
cmd.list$colClasses <- c("character", rep(colClasses1$getActiveText(), NN-1))
}
cmd.list$nrows=nrows
}
xx <- do.call(GetTable, cmd.list) # read it with row names NULL
# ignore a percentage sign too
#num.string.ignore = paste("[", NUMBER_FORMATS["thousands", dec1$getActiveText()], num.ignore.string1$getText(), "]", sep="")
num.string.ignore = num.ignore.string1$getText()
xx <- CheckStringsForNumeric(xx, num.string.ignore)
# options for pasting into the table after reading
other.opts = list(
paste.row.labels = paste.row.labels$getActive(),
paste.col.labels = paste.col.labels$getActive()
)
if(!is.null(row.names.arg) && length(dim(xx))==2 && 0 < row.names.arg && row.names.arg <= ncol(xx)){
#dupes <- duplicated(xx[,row.names.arg])
#if(any(dupes)) {
# xx <- xx[!dupes,,drop=F]
# #if(doMsg) message("Dropping ", sum(dupes), " row(s) from table to create unique row names")
#}
xr <- as.character(xx[,row.names.arg])
dupes <- duplicated(xr)
nas <- is.na(xr)
if(any(nas)) xr[nas] <- ""
if(any(dupes)) xr <- make.unique(xr)
rownames(xx) <- xr
xx <- xx[,-row.names.arg,drop=F]
}
return(list(xx=xx, cmd.list=cmd.list, other.opts=other.opts))
} # end read_table
update_view = function(obj=NULL, label=NULL){
tryCatch({
preview <- read_table(500)$xx
#fr3$setLabel(paste("Table Size:", nrow(preview), "R x", ncol(preview), "C"))
obj <- gtkDfEdit(preview, size.request = c(400, 200), col.width=100, editable=FALSE)
theObj = gtkContainerGetChildren(box3)[[1]]
if(!is.null(theObj))
gtkWidgetDestroy(theObj)
gtkBoxPackEnd(box3, obj)
}, error = function(e) {
tb3 <- gtkTextViewNew()
tb3$setWrapMode(GtkWrapMode['word_char'])
tb3$setSizeRequest(400, 200)
tb3$getBuffer()$setText(as.character(e$message))
theObj = gtkContainerGetChildren(box3)[[1]]
if(!is.null(theObj))
gtkWidgetDestroy(theObj)
box3$packEnd(tb3)
})
}
#gSignalConnect(preview.button, "clicked", update_view)
sapply(list(row.names0, header1, fill1, stringsAsFactors1, check.names1),
function(x) tryCatch(gSignalConnect(x, "toggled", update_view), error=function(e) print(e)))
sapply(list(num.ignore.string1, sep1, skip1, colClasses1, quote1, na.strings1),
function(x) tryCatch(gSignalConnect(x, "changed", update_view), error=function(e) print(e)))
gSignalConnect(dec1, "changed",
function(...){
all.ign <- paste(c("[", NUMBER_FORMATS["thousands",], "]"), collapse="")
txt <- gsub(all.ign, "", num.ignore.string1$getText())
new.ign <- NUMBER_FORMATS["thousands", dec1$getActiveText()]
num.ignore.string1$setText(paste(txt, new.ign, sep=""))
}
)
gSignalConnect(row.names0, "toggled",
function(...){
paste.row.labels$setActive(row.names0$getActive())
}
)
gSignalConnect(header1, "toggled",
function(...){
paste.col.labels$setActive(header1$getActive())
}
)
fr3$add(box3)
box0$packStart(fr3)
box0$packStart(fr2, FALSE, FALSE, 0)
dialog[["vbox"]]$packStart(box0, TRUE, TRUE, 10)
dialog$showAll()
update_view()
xx <- NULL
rv <- NULL
if(dialog$run() == 1){# && nchar(entry1$getText())){
w <- TransientWindow("Copying...")
on.exit(w$destroy())
tryCatch({
rv <- read_table(doMsg=TRUE)
xx <- rv$xx
cmd.list = rv$cmd.list
#aa <- basename(entry1$getText())
#newname <- make.names(strsplit(aa, "[.]")[[1]][1])
#assign(newname, xx, envir=.GlobalEnv)
}, error = function(e) {print(e)})
dialog$destroy()
} else {
dialog$destroy()
}
return(rv)
#return(xx)
}
# from gWidgets
gtkMenuPopupHack <- function (object, parent.menu.shell = NULL, parent.menu.item = NULL,
func = NULL, data = NULL, button, activate.time) {
checkPtrType(object, "GtkMenu")
if (!is.null(parent.menu.shell))
checkPtrType(parent.menu.shell, "GtkWidget")
if (!is.null(parent.menu.item))
checkPtrType(parent.menu.item, "GtkWidget")
if (!is.null(func))
func <- as.function(func)
button <- as.numeric(button)
activate.time <- as.numeric(activate.time)
w <- .RGtkCall("S_gtk_menu_popup", object, parent.menu.shell,
parent.menu.item, func, data, button, activate.time,
PACKAGE = "RGtk2")
return(invisible(w))
}
###############################################################################
# Factor editor
###############################################################################
##########################################
# Blocking editor
BlockSizeHandler <- function(the.column, data){
.local <- data$.local
row.idx = data$row.idx
col.idx = data$col.idx
entry.frame <- data.frame(X=the.column)
task <- list(
list(func="ChangeCells",
arg = list(nf=entry.frame, row.idx=row.idx, col.idx=col.idx))
)
DoTaskWrapper(.local, task)
}
# start.lvl = level name to start cycling at
DoBlockSize <- function(the.column, loc.window, handler, data, start.lvl=NULL){
.BlockEnv <- new.env()
.BlockEnv$the.column <- the.column
.BlockEnv$data <- data
UpdateColumn <- function(block.size, dummy){
total.len <- length(.BlockEnv$the.column)
.BlockEnv$the.column <- gl(length(lvls), block.size, total.len, labels=lvls)
#handler(the.column, data)
}
if(!is.factor(the.column)) stop("Can't block over non-factors")
lvls <- unique(levels(the.column))
if(!is.null(start.lvl)){
if(!start.lvl%in%lvls) stop("start.level must be in levels")
ww <- which(lvls == start.lvl)[1]
if(ww > 1) lvls <- c(lvls[ww:length(lvls)], lvls[1:(ww-1)])
}
UpdateColumn(1)
ok.handler <- function(block.size, dummy){
handler(.BlockEnv$the.column, .BlockEnv$data)
}
MakeSpinDialog(loc.window, title = "Blocking", label="Select Block Size", spin.handler=UpdateColumn, ok.handler=ok.handler, data=NULL)
}
MakeSpinDialog <- function(loc.window, title, label, spin.handler, ok.handler, data=NULL, maximum=100.0, minimum=1.0){
window2 <- gtkWindowNew(show=F)
window2$setTitle(title)
window2$setPosition(GtkWindowPosition["center-on-parent"])
window2$setTransientFor(loc.window)
window2$setModal(TRUE)
box0 <- gtkVBoxNew(FALSE, 5)
window2$add(box0)
window2$setResizable(FALSE)
window2$setPosition(GtkWindowPosition["center-on-parent"])
if(!is.null(loc.window)) window2$setTransientFor(loc.window)
window2$setModal(TRUE)
box1 <- gtkHBoxNew(FALSE, 5)
box0$packStart(box1, FALSE, FALSE, 10)
box1$packStart(gtkLabelNew(label), FALSE, FALSE, 10)
spinner_adj <- gtkAdjustment(1, 1, maximum, minimum, 5.0)
spinner <- gtkSpinButton(spinner_adj, 1.0, 0)
spinner$setValue(1)
box1$add(spinner)
spinner$grabFocus()
box2 <- gtkHBoxNew(FALSE, 5)
box0$packEnd(box2, FALSE, FALSE, 0)
button <- gtkButtonNewWithLabel(" OK ")
gSignalConnect(spinner, "value-changed", function(...){
spin.handler(spinner$getValue(), data)
})
gSignalConnect(spinner, "key-press-event", function(obj, evt){
if(evt[["keyval"]] == GDK_Return) {
spinner$update()
ok.handler(spinner$getValue(), data)
window2$destroy()
}
if(evt[["keyval"]] == GDK_Escape) {
window2$destroy()
}
FALSE
})
gSignalConnect(button, "clicked", function(handler, data){
spinner$update()
ok.handler(spinner$getValue(), data)
window2$destroy()
})
box2$packEnd(button, FALSE, FALSE, 0)
button <- gtkButtonNewWithLabel(" Cancel ")
gSignalConnect(button, "clicked", function(...){
window2$destroy()
})
box2$packEnd(button, FALSE, FALSE, 5)
window2$show()
}
FactorEditorHandler <- function(the.column, col.idx, data){
.local <- data
#print(.FactorEnv$the.column)
# the.column <- myDataTypeCoercions("factor", the.column)
# x <- sub("^[[:space:]]*(.*?)[[:space:]]*$", "\\1", x, perl=TRUE)
# x[x=="NA"|nchar(x) == 0] <- NA
entry.frame <- data.frame(X=the.column)
the.contrasts <- NULL
if(length(levels(the.column)) > 1) the.contrasts <- contrasts(the.column)
task <- list(list(func="ChangeCells",
arg = list(nf=entry.frame, col.idx=col.idx)),
list(func="SetFactorAttributes",
arg = list(col.idx=col.idx, info=list(levels=levels(the.column),
contrasts=the.contrasts, contrast.name=attr(the.column, "contrast.name"), ordered=is.ordered(the.column))))
)
DoTaskWrapper(.local, task)
}
DoFactorEditor <- function(theFrame, toplevel, col.idx=integer(0),
handler=NULL, data=NULL){
.FactorEnv <- new.env()
.FactorEnv$col.idx <- col.idx
UpdateView <- function(){
contr <- contrast.coding[[which(sapply(rev(grb$getGroup()), gtkToggleButtonGetActive))]]
x <- .FactorEnv$the.column
if(ordered.checkbox$getActive() != is.ordered(x))
x <- factor(x, levels = levels(x), ordered = ordered.checkbox$getActive())
if(length(levels(x)) > 1) {
contrasts(x) <- contr
attr(x, "contrast.name") <- contr
}
handler(x, .FactorEnv$col.idx , data)
.FactorEnv$the.column <- x
}
cell.edited <- function(cell, path.string, new.text, data){
xx <- .FactorEnv$xx
if(!nchar(new.text))# || new.text%in%xx)
stop("New name must exist")# and be unique")
checkPtrType(data, "GtkListStore")
model <- data
path <- gtkTreePathNewFromString(path.string)
iter <- model$getIter(path)$iter
i <- path$getIndices()[[1]]+1
# editing the level names # 101010
#zz <- theFrame[,.FactorEnv$col.idx]
zz <- .FactorEnv$the.column# <- factor(.FactorEnv$the.column, levels=unique(xx))
lzz <- levels(zz)
ww <- which(xx[i]==lzz)
if(length(ww)){
lzz[ww] <- new.text
levels(zz) <- lzz
.FactorEnv$the.column <- zz
UpdateView()
}
xx[i] <- new.text
model$set(iter, 0, new.text)
.FactorEnv$xx <- xx
UpdateLabel()
}
add.item <- function(button, data) {
xx <- .FactorEnv$xx
for(k in 1:(length(xx)[1]+1)){
nl <- paste("Level", k, sep="_")
if(!nl%in%xx) break
}
xx <- c(xx, nl)
.FactorEnv$xx <- xx
iter <- model$append()$iter
model$set(iter, 0, xx[length(xx)])
.FactorEnv$the.column <- factor(.FactorEnv$the.column, levels=unique(xx))
UpdateView()
UpdateLabel()
}
remove.item <- function(widget, data)
{
xx <- .FactorEnv$xx
checkPtrType(data, "GtkTreeView")
treeview <- data
model <- treeview$getModel()
selection <- treeview$getSelection()
selected <- selection$getSelected()
if (selected[[1]]){
iter <- selected$iter
path <- model$getPath(iter)
i <- path$getIndices()[[1]]+1
model$remove(iter)
xx <- xx[-i]
.FactorEnv$xx <- xx
path$prev()
selection$selectPath(path)
.FactorEnv$the.column <- factor(.FactorEnv$the.column, levels=unique(xx))
UpdateView()
UpdateLabel()
}
}
move.item.up <- function(widget, data)
{
xx <- .FactorEnv$xx
checkPtrType(data, "GtkTreeView")
treeview <- data
model <- treeview$getModel()
selection <- treeview$getSelection()
selected <- selection$getSelected()
if (selected[[1]])
{
iter <- selected$iter
path <- model$getPath(iter)
i <- path$getIndices()[[1]]+1
if(i == 1) return()
model$set(iter, 0, xx[i-1])
path$prev()
selection$selectPath(path)
selected <- selection$getSelected()
iter <- selected$iter
model$set(iter, 0, xx[i])
tmp <- xx[i-1]
xx[i-1] <- xx[i]
xx[i] <- tmp
.FactorEnv$xx <- xx
.FactorEnv$the.column <- factor(.FactorEnv$the.column, levels=unique(xx))
UpdateView()
UpdateLabel()
}
}
move.item.down <- function(widget, data)
{
xx <- .FactorEnv$xx
checkPtrType(data, "GtkTreeView")
treeview <- data
model <- treeview$getModel()
selection <- treeview$getSelection()
selected <- selection$getSelected()
if (selected[[1]])
{
iter <- selected$iter
path <- model$getPath(iter)
i <- path$getIndices()[[1]]+1
if(i == length(xx)) return()
model$set(iter, 0, xx[i+1])
gtkTreePathNext(path)
selection$selectPath(path)
selected <- selection$getSelected()
iter <- selected$iter
model$set(iter, 0, xx[i])
tmp <- xx[i+1]
xx[i+1] <- xx[i]
xx[i] <- tmp
.FactorEnv$xx <- xx
.FactorEnv$the.column <- factor(.FactorEnv$the.column, levels=unique(xx))
UpdateView()
UpdateLabel()
}
}
edit.item <- function(widget, data) {
checkPtrType(data, "GtkTreeView")
treeview <- data
model <- treeview$getModel()
selection <- treeview$getSelection()
selected <- selection$getSelected()
if (selected[[1]])
{
iter <- selected$iter
path <- model$getPath(iter)
treeview$setCursorOnCell(path,
treeview$getColumns()[[1]],
treeview$getColumns()[[1]]$getCellRenderers()[[1]],
TRUE)
UpdateLabel()
}
}
contrast.coding <- list(
"Treatment (default) - contrasts each level with the first.\nThe first level is omitted." = "contr.treatment",
"Helmert - contrast the second level with the first, the \nthird with the average of the first two, and so on." = "contr.helmert",
"Polynomial - contrasts based on orthogonal polynomials." = "contr.poly",
"Sum - sum to zero contrasts." = "contr.sum",
"SAS - treatment contrasts with base level set to be the\nlast level of the factor." = "contr.SAS")
UpdateLabel <- function(){
xx <- .FactorEnv$xx
contr <- contrast.coding[[which(sapply(rev(grb$getGroup()), gtkToggleButtonGetActive))]]
contrF <- paste("Control (first level) is: <b>", xx[1], "</b>", sep="")
contrL <- paste("Control (last level) is: <b>", rev(xx)[1], "</b>", sep="")
contr.msg <- list(contr.treatment = contrF, contr.helmert = contrL,
contr.poly = "L and Q terms (see MASS, p156)",
contr.sum = contrL, contr.SAS = contrL)[[contr]]
lab1$setMarkup(contr.msg)
}
.FactorEnv$the.column <- theFrame[,.FactorEnv$col.idx]
.FactorEnv$col.original <- .FactorEnv$the.column
contr <- attr(.FactorEnv$the.column, "contrast.name")
box4 <- gtkVBoxNew(FALSE, 5)
grb <- gtkRadioButtonNewWithLabel(NULL, label=names(contrast.coding)[1])
grb$setActive(TRUE)
box4$packStart(grb, FALSE, FALSE, 0)
for(ii in 2:length(names(contrast.coding))){
grb <- gtkRadioButtonNewWithLabel(group=grb$getGroup(),
label=names(contrast.coding)[ii])
if(!is.null(contr) && contr==unlist(contrast.coding)[ii]) grb$setActive(TRUE)
gSignalConnect(grb, "toggled", function(grb, ...) if(grb$getActive()) UpdateLabel())
box4$packStart(grb, FALSE, FALSE, 0)
}
data.factors <- which(GetClasses(theFrame) == "factor")
if(!length(colnames(theFrame)[data.factors])) stop("No data columns are of type \"factor\"")
# called with no selection
if(!length(col.idx)) {
col.idx <- data.factors[1]
.FactorEnv$col.idx <- col.idx
} else if(length(col.idx==1) && !is.factor(theFrame[,col.idx])) {
stop(paste("Data column:", col.idx, "is not of type \"factor\""))
}
# window <- gtkWindowNew(show=F)
dialog <- gtkDialog("Factor Editor", NULL, "modal", "gtk-ok", 1, "gtk-cancel", 0, show = T)
dialog$setPosition(GtkWindowPosition["center-on-parent"])
dialog$setTransientFor(toplevel)
# if(!is.null(toplevel)) {
# window$setTransientFor(toplevel)
# window$setModal(TRUE)
# }
dialog$setTitle("Factor Editor")
# window$setBorderWidth(5)
box0 <- gtkVBoxNew(FALSE, 5)
dialog[["vbox"]]$add(box0)
# window$add(box0)
fr0 <- gtkFrameNew(label="Column Selection")
cb1 <- gtkComboBoxNewText()
cb1$show()
cb1["width-request"] <- 75
for (item in colnames(theFrame)[data.factors]) # omit "rows" heading
cb1$appendText(item)
theIdx <- which(data.factors %in% col.idx)-1
cb1$setActive(theIdx)
fr0$add(cb1)
box0$packStart(fr0, FALSE, FALSE, 5)
fr1 <- gtkFrameNew(label="Factor Level Order")
box0$packStart(fr1, TRUE, TRUE, 5)
box1 <- gtkHBoxNew(FALSE, 5)
fr1$add(box1)
box1.5 <- gtkVBoxNew(FALSE, 0)
box1$packStart(box1.5, TRUE, TRUE, 5)
box1.6 <- gtkHBoxNew(FALSE, 0)
box1.5$packStart(box1.6, FALSE, TRUE, 5)
lab1 <- gtkLabelNew("")
box1.6$packStart(lab1, FALSE, FALSE, 5)
MakeModel <- function(){
col.original <- .FactorEnv$col.original
if(!is.factor(col.original)) {
col.original <- as.factor(integer(0))
#stop("Can't edit non-factors")
warning("Can't edit non-factors")
}
.FactorEnv$xx <- na.omit(cbind(levels(col.original)))
#print(col.original)
#print(levels(col.original))
#.FactorEnv$xx <- cbind(levels(col.original))
model <- gtkListStoreNew("gchararray")
xx <- .FactorEnv$xx
sapply(xx, function(x) model$set(model$append()$iter, 0, x))
UpdateLabel()
return(model)
}
sw <- gtkScrolledWindowNew(NULL, NULL)
sw$setPolicy("automatic", "automatic")
box1.5$packStart(sw, TRUE, TRUE, 0)
treeview <- gtkTreeViewNew()
model <- MakeModel()
treeview$setModel(model)
gSignalConnect(cb1, "changed", function(widget){
new.idx <- which(widget$getActiveText()==colnames(theFrame))
.FactorEnv$col.idx <- new.idx
.FactorEnv$the.column <- theFrame[,new.idx]
.FactorEnv$col.original <- .FactorEnv$the.column
model <- MakeModel()
treeview$setModel(model)
})
treeview$setRulesHint(TRUE)
treeview$setHeadersVisible(FALSE)
treeview$setEnableSearch(FALSE)
treeview$getSelection()$setMode("single")
renderer <- gtkCellRendererTextNew()
renderer$setData("column", 0)
renderer['editable-set'] <- TRUE
renderer['editable'] <- TRUE
treeview$insertColumnWithAttributes(-1, "Name", renderer, text = 0)
sw$setShadowType(as.integer(1))
sw$add(treeview)
sw$setSizeRequest(300, -1)
# window$setResizable(FALSE)
dialog$setResizable(FALSE)
gSignalConnect(renderer, "edited", cell.edited, model)
box2 <- gtkVBoxNew(FALSE, 5)
box1$packStart(box2, FALSE, FALSE, 5)
button1 <- gtkButtonNewWithLabel("Move Up")
box2$packStart(button1, FALSE, FALSE, 0)
button2 <- gtkButtonNewWithLabel("Move Down")
box2$packStart(button2, FALSE, FALSE, 0)
button3 <- gtkButtonNewWithLabel("Edit Name")
box2$packStart(button3, FALSE, FALSE, 0)
button4 <- gtkButtonNewWithLabel("Add Level")
box2$packStart(button4, FALSE, FALSE, 0)
button5 <- gtkButtonNewWithLabel("Remove Level")
box2$packStart(button5, FALSE, FALSE, 0)
gSignalConnect(button1, "clicked", move.item.up, data=treeview)
gSignalConnect(button2, "clicked", move.item.down, data=treeview)
gSignalConnect(button3, "clicked", edit.item, data=treeview)
gSignalConnect(button4, "clicked", add.item, model)
gSignalConnect(button5, "clicked", remove.item, treeview)
ordered.checkbox <- gtkCheckButtonNewWithLabel(label="Levels Are Ordered")
ordered.checkbox$setActive(is.ordered(.FactorEnv$the.column))
box0$packStart(ordered.checkbox)
expander <- gtkExpanderNew(label="Factor Contrasts (For Experts)")
fr2 <- gtkFrameNew(label="Contrast Coding")
#box0$packStart(fr2, FALSE, FALSE, 5)
box0$packStart(expander, FALSE, FALSE, 5)
expander$add(fr2)
fr2$add(box4)
# box6 <- gtkHBoxNew(FALSE, 5)
UpdateLabel()
if(dialog$run() == 1){
UpdateView()
dialog$destroy()
} else {
.FactorEnv$the.column <- .FactorEnv$col.original
UpdateView()
dialog$destroy()
}
# button <- gtkButtonNewWithLabel(" Cancel ")
# gSignalConnect(button, "clicked", function(...) {
# .FactorEnv$the.column <- .FactorEnv$col.original
# UpdateView()
# window$destroy()
# })
# box6$packEnd(button, FALSE, FALSE, 5)
#
# box0$packEnd(box6, FALSE, FALSE, 0)
# button <- gtkButtonNewWithLabel(" OK ")
# gSignalConnect(button, "clicked", function(...){
# UpdateView()
# window$destroy()
# })
# box6$packEnd(button, FALSE, FALSE, 0)
#
# window$show()
}
###############################################################################
# End factor editor
###############################################################################
#TableEntryDialog <- function(.localenv){
MakeRadioDialog <- function(title, items, handler=NULL, data=NULL){
window <- gtkWindowNew(show=F)
if(!is.null(data$.local)) {
window$setPosition(GtkWindowPosition["center-on-parent"])
window$setTransientFor(data$.local$toplevel)
window$setModal(TRUE)
}
window$setTitle(title)
window$setBorderWidth(5)
box0 <- gtkVBoxNew(FALSE, 5)
window$add(box0)
window$setResizable(FALSE)
window$resize(window$getSize()$width, 1)
xx <- list()
for(jj in 1:length(items)){
fr1 <- gtkFrameNew(label=names(items[[jj]]))
xx[[jj]] <- MakeRadiobuttonGroup(items[[jj]][[1]])
fr1$add(xx[[jj]]$box)
box0$add(fr1)
}
fr2 <- gtkVBoxNew()
box18000 <- gtkHBoxNew(FALSE, 5)
fr2$add(box18000)
button <- gtkButtonNewWithLabel(" Cancel ")
gSignalConnect(button, "clicked", function(...){
window$destroy()
})
box18000$packEnd(button, FALSE, FALSE, 0)
button <- gtkButtonNewWithLabel(" OK ")
box18000$packEnd(button, FALSE, FALSE, 0)
gSignalConnect(button, "clicked", function(...){
results<- sapply(xx, function(item)
which(rev(sapply(item$grb$getGroup(), gtkToggleButtonGetActive))))
res <- list()
for(jj in 1:length(items))
res[[names(items[[jj]])]] <- items[[jj]][[1]][results[jj]]
if(!is.null(handler)) handler(res, data)
window$destroy()
})
box0$packStart(fr2, FALSE, FALSE, 0)
window$show()
}
TABLE_IMPORT_OPTIONS <- list(
list("Data Type" = c("Numeric Data", "Character Data")),
list("Name Options" = c("Row And Column Names", "Row Names Only", "Column Names Only", "No Row Or Column Names"))
)
TABLE_IMPORT_OPTIONS_FUNC <- function(results){
do.colnames <- F; do.rownames <- F
if(results$"Name Options"%in%c("Row And Column Names", "Row Names Only"))
do.rownames <- T
if(results$"Name Options"%in%c("Row And Column Names", "Column Names Only"))
do.colnames <- T
colClasses <- NA
if(results$"Data Type"%in%c("Numeric Data"))
colClasses <- "numeric"
if(results$"Data Type"%in%c("Character Data"))
colClasses <- "character"
list(colClasses=colClasses, do.rownames=do.rownames, do.colnames=do.colnames)
}
###############################################################################
# Sort dialog
###############################################################################
MakeRadiobuttonGroup <- function(labels, theChoice=1){
box3 <- gtkVBoxNew()
grb1 <- gtkRadioButtonNewWithLabel(NULL, label=labels[1])
if(theChoice == 1) grb1$setActive(TRUE)
box3$add(grb1)
for(jj in 2:length(labels)){
grb1 <- gtkRadioButtonNewWithLabel(group=grb1$getGroup(),label=labels[jj])
if(jj == theChoice) grb1$setActive(TRUE)
box3$add(grb1)
}
return(list(box=box3, grb=grb1))
}
MakeComboEntry <- function(items, box1){
box2 <- gtkHBoxNew()
cb1 <- gtkComboBoxNewText()
cb1$show()
cb1["width-request"] <- 100
for (item in items) # omit "rows" heading
cb1$appendText(item)
cb1$setActive(0)
box5 <- gtkVBoxNew()
box5$packStart(cb1, TRUE, FALSE, 0)
box2$packStart(box5, TRUE, TRUE, 10)
xx1 <- MakeRadiobuttonGroup(c("Low to high", "High to low"))
box3 <- xx1$box
grb1 <- xx1$grb
xx2 <- MakeRadiobuttonGroup(c("Default", "Character", "Numeric"))
box4 <- xx2$box
grb2 <- xx2$grb
box2$packStart(box4, FALSE, FALSE, 0)
box2$packStart(box3, FALSE, FALSE, 0)
box1$packStart(box2, FALSE, FALSE, 0)
return(list(col=cb1, ord=grb1, typ=grb2, box=box2))
}
# Returns the ordering of the table
DoSortDialog <- function(theFrame, handler, .localenv){
window <- gtkWindowNew(show=F)
if(!is.null(.localenv$toplevel)) {
window$setPosition(GtkWindowPosition["center-on-parent"])
window$setModal(TRUE)
window$setTransientFor(.localenv$toplevel)
}
window$setTitle("Sort Options")
window$setBorderWidth(5)
box0 <- gtkVBoxNew(FALSE, 5)
window$add(box0)
items <- colnames(theFrame)
fr0 <- gtkFrameNew(label="Order By Column")
box1 <- gtkVBoxNew(FALSE, 5)
box0$add(fr0)
fr0$add(box1)
.sl <- new.env()
.sl$theList <- list()
.sl$theList[[length(.sl$theList)+1]] <- MakeComboEntry(items, box1)
fr1 <- gtkFrameNew(label="Add/Remove Columns")
box9000 <- gtkHBoxNew(FALSE, 5)
fr1$add(box9000)
button <- gtkButtonNewWithLabel("- Column")
gSignalConnect(button, "clicked", function(obj, ...){
if(length(.sl$theList)<2) return(FALSE)
.sl$theList[[length(.sl$theList)]]$box$destroy()
.sl$theList[[length(.sl$theList)]] <- NULL
window$resize(window$getSize()$width, 1)
})
box9000$packEnd(button, FALSE, FALSE, 0)
button <- gtkButtonNewWithLabel("+ Column")
gSignalConnect(button, "clicked", function(obj, data=.sl){
.sl$theList[[length(.sl$theList)+1]] <- MakeComboEntry(items, box1)
})
box9000$packEnd(button, FALSE, FALSE, 0)
box0$packStart(fr1, FALSE, FALSE, 0)
fr2 <- gtkVBoxNew()
box18000 <- gtkHBoxNew(FALSE, 5)
fr2$add(box18000)
button <- gtkButtonNewWithLabel(" Cancel ")
gSignalConnect(button, "clicked", function(obj, data=.sl){
window$destroy()
})
box18000$packEnd(button, FALSE, FALSE, 0)
button <- gtkButtonNewWithLabel(" OK ")
box18000$packEnd(button, FALSE, FALSE, 0)
gSignalConnect(button, "clicked", function(obj, data=.sl){
opts <- lapply(.sl$theList, function(item){
list(col = item$col$getActiveText(),
ord = which(rev(sapply(item$ord$getGroup(), gtkToggleButtonGetActive))),
typ = which(rev(sapply(item$typ$getGroup(), gtkToggleButtonGetActive))))
})
dataset.order <- do.call("order", lapply(opts, function(item){
xx <- theFrame[[item$col]]
if(item$typ == 1) {
xrank <- xtfrm(xx)
} else if (item$typ == 2) {
xrank <- xtfrm(as.character(xx))
} else if (item$typ == 3) {
xrank <- xtfrm(as.numeric(xx))
} else {
stop("Sort error")
}
(c(-1, 1)[(item$ord==1)+1])*xrank
}))
handler(dataset.order, .localenv)
window$destroy()
})
box0$packStart(fr2, FALSE, FALSE, 0)
window$setResizable(FALSE)
window$show()
}
###############################################################################
# End sort dialog
###############################################################################
DoTaskWrapper <- function(.local, task, do.undo = TRUE){
#tryCatch({
#print("DoTaskWrapper")
rv <- DoTask(.local, .local$theFrame, task, .local$changed.handler)
rows.changed <- NULL
# Special for changecells to minimize edit time
if(length(task) == 1 && task[[1]]$func == "ChangeCells")
rows.changed <- task[[1]]$arg$row.idx
#print("Updating here...")
#print(dim(.local$model))
UpdateDfEditor(.local, rv$df, rows.changed)
#print("Updating finished")
#print(dim(.local$model))
if(do.undo){
.local$undoStack[[length(.local$undoStack)+1]] <- rv$undo
if(object.size(.local$undoStack) > STACK_LIMIT){
warning("Stack full")
jj <- 0
while(object.size(.local$undoStack) > STACK_LIMIT && length(.local$undoStack))
.local$undoStack[[jj <- jj + 1]] <- NULL
if(object.size(.local$undoStack) > STACK_LIMIT){
warning("This edit is too large to support undo")
.local$undoStack <- list()
}
}
}
#}, error = function(e) {
# warning("An error has occurred in performing the task")
# old_warning(e)
#})
}
# update the scroll in one treeview from another
ViewOnScrollChanged <- function(obj, data){
sw2 <- data$sw2
.local <- data$.local
# Take over the event loop!
# See http://wiki.laptop.org/go/PyGTK/Smooth_Animation_with_PyGTK
while(gtkEventsPending())
gtkMainIteration()
gtkAdjustmentSetValue(sw2, gtkAdjustmentGetValue(obj))
.local$allow.key.flag <- TRUE # For paging, however, this has a problem...
}
############################################################
# Turns out that scroll_row_timeout has no horizontal autoscroll.
# I based this on gtktreeview::gtk_tree_view_vertical_autoscroll, no offset
HScroll <- function(data){
.local <- data
# which side of the middle are we?
view <- .local$view
sw.ha <- .local$sw.ha
ptr <- view$getBinWindow()$getPointer()
vr <- view$getVisibleRect()$visible.rect
sw.ha.value <- sw.ha$value
direction <- ifelse (ptr$x - sw.ha.value <= vr$width/2, -1, 1)
val <- sw.ha.value + direction*sw.ha[["step_increment"]]
if(0 <= val && val <= sw.ha[["upper"]] - sw.ha[["page_size"]]) {
sw.ha$setValue(val)
} else if (val < 0) {
sw.ha$setValue(0)
} else {
sw.ha$setValue(sw.ha[["upper"]] - sw.ha[["page_size"]])
}
TRUE
}
# Bind this to button-release, focus-out and enter-notify
RemoveHScrollTimeout <- function(obj, event, data){
.local <- data
try({
gSourceRemove(.local$hScrollTimeout)
.local$doingHScroll <- FALSE
}, silent=T)
FALSE
}
AddHScrollTimeout <- function(obj, event, data){
.local <- data
sw.ha <- .local$sw.ha
view <- .local$view
if (!.local$doingHScroll){
ptr <- obj$getBinWindow()$getPointer()
if (as.flag(ptr$mask) & GdkEventMask['button-press-mask']){
x <- ptr$x - sw.ha$getValue()
y <- ptr$y
vr <- view$getVisibleRect()$visible.rect
h <- vr$height
w <- vr$width
z1 <- y > h/w*x
z2 <- y > h - h/w*x
if((z1 && !z2) || (!z1 && z2)){
.local$hScrollTimeout <- gTimeoutAdd(.local$SCROLL_ROW_TIMEOUT, HScroll, data=.local)
.local$doingHScroll <- TRUE
}
}} #if, if
TRUE
}
############################################################
# End autoscroll
PaintSelectionOnTimeout <- function(.local, selection=.local$ss, widget=.local$view){
# We want to add a timeout to the LAST key pressed.
try({
gSourceRemove(.local$do.paint.timeout)
.local$do.paint.timeout <- NULL
}, silent=T)
.local$do.paint.timeout <- gTimeoutAdd(100, function(){
.local$do.paint <- TRUE
UpdateSelectionRectangle(.local, selection, widget)
return(FALSE)
})
}
####
############################################################
MoveCursor <- function(widget, direction, .local, stat=as.integer(0)){
cursor.info <- gtkTreeViewGetCursor(widget)
path <- cursor.info$path
column <- cursor.info$focus.column
allColumns <- .local$allColumns
page.flag <- FALSE
if(is.null(path)){
row.idx = 1
new.idx = 2
} else {
row.idx <- as.integer(gtkTreePathGetIndices(path))+1
new.idx <- GetColIdx(cursor.info$focus.column)
}
col.idx <- new.idx
if(IsShift(stat)){
if(is.null(.local$selections))
.local$selections <- list(list(start=c(row.idx=row.idx, col.idx=col.idx)))
}
if (direction %in% c("page_down", "page_up")){
page.flag <- TRUE
visible_row_range <- as.integer(diff(sapply(gtkTreeViewGetVisibleRange(widget)[-1], gtkTreePathGetIndices)))
row.idx <- row.idx +
ifelse(direction=="page_down", 1, -1)*visible_row_range
if(direction=="page_down") row.idx <- row.idx-1
else if(direction=="page_up") row.idx <- row.idx
if(row.idx < 1)
row.idx <- 1
else if (row.idx > nrow(.local$theFrame))
row.idx <- nrow(.local$theFrame)
path <- gtkTreePathNewFromString(row.idx-1)
gtkTreeViewScrollToCell(widget, path, column=NULL, use.align=TRUE, row.align=0)
gtkTreeViewSetCursorOnCell(widget, path, NULL, NULL, FALSE)
} else if (direction == "right"){
if( new.idx < length(allColumns)){
new.idx <- new.idx+1
} else {
if(.local$editable) insert.dialog(.local, row.idx=row.idx-1, col.idx=new.idx, insert.type ="Columns", win=.local$toplevel)
}
} else if (direction == "left") {
if(1 < new.idx)
new.idx <- new.idx-1
} else if (direction == "down") {
if (row.idx < nrow(.local$theFrame)){
gtkTreePathNext(path)
row.idx <- row.idx+1
} else {
if(.local$editable) insert.dialog(.local, row.idx=row.idx-EXTRA_ROW, col.idx=new.idx, insert.type ="Rows", win=.local$toplevel)
}
} else if (direction == "up") {
gtkTreePathPrev(path)
if(row.idx > 1) row.idx <- row.idx-1
} else if (direction%in%c("home", "end")) {
row.idx <- ifelse(direction=="home", 1, nrow(.local$theFrame))
path <- gtkTreePathNewFromString(row.idx-1)
gtkTreeViewScrollToCell(widget, path, column=NULL, use.align=TRUE, row.align=0)
gtkTreeViewSetCursorOnCell(widget, path, NULL, NULL, FALSE)
}
if(as.flag(stat) & GdkModifierType['shift-mask']){
stopifnot(length(.local$selections)>0)
.local$selections[[1]]$end <- c(row.idx=row.idx, col.idx=new.idx)
gdkWindowInvalidateRect(.local$viewGetBinWindow, NULL, FALSE)
PaintSelectionOnTimeout(.local)
} else {
# this won't get called, we caught these in ViewKeyPress
if (as.flag(stat) & GdkModifierType['control-mask']) {
} else {
if(!is.null(.local$selections)){
.local$selections <- NULL
UpdateSelectionRectangle(.local)
}
UpdateColumnSelection(.local, new.idx)
}
}
if(!page.flag){
new.col <- allColumns[[new.idx]]$column
renderer <- allColumns[[new.idx]]$renderer
gtkTreeViewSetCursorOnCell(widget, path, new.col, renderer, FALSE)
}
# ScrollToCell seems to stop working after we add a new column.
# This routine is from gtktreeview::gtk_tree_view_scroll_to_cell
if(direction%in%c("left", "right")){
cell_rect <- gtkTreeViewGetBackgroundArea(widget, path, new.col)$rect
vis_rect <- gtkTreeViewGetVisibleRect(widget)$visible.rect
dest_x <- vis_rect$x
dest_y <- vis_rect$y
if (cell_rect$x < vis_rect$x)
dest_x <- cell_rect$x
if (cell_rect$x+cell_rect$width > vis_rect$x + vis_rect$width)
dest_x <- cell_rect$x + cell_rect$width - vis_rect$width
gtkTreeViewScrollToPoint(widget, dest_x, dest_y)
}
selections <- list(
list(start=c(row.idx=row.idx, col.idx=new.idx),
end=c(row.idx=row.idx, col.idx=new.idx)))
# update the selection only if we didn't select anything else
if(!length(.local$selections)) DoUserCall("Selection", list(selections=selections), .local)
#} # if 0
return(TRUE)
}
RowNamesClearContents <- function(row.idx, .local){
if(length(row.idx)==0) return(FALSE)
nf <- .local$theFrame[row.idx,, drop=F]
nf[,-1] <- ""
task <- list(list(func="ChangeCells",
arg = list(nf=nf, row.idx=row.idx)))
DoTaskWrapper(.local, task)
}
GetColIdx <- function(column){
#tryCatch(
as.integer(column["title"])#,
# error = function(e) integer(0))
}
RowNamesKeyPress <- function(widget, event, data) {
.local <- data
keyval <- event[["keyval"]]
stat <- as.flag(event[["state"]])
#print(keyval)
if (CtrlLetter(keyval, stat, GDK_z)){
#print("Ctrl-z")
DoUndo(.local)
return(TRUE)
} else if(keyval == GDK_Delete && .local$editable) {
#row.idx <- GetSelectedRows(.local$view.rn, .local)
#RowNamesClearContents(row.idx, .local)
DeleteSelection(.local)
#.local$allow.key.flag <- TRUE
return(TRUE)
} else if (CtrlLetter(keyval, stat, GDK_c)) {
# block transient interactions with a popup
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
nf <- GetSelectedCells(.local)
if(!is.null(nf)) CopyToClipboard(nf,
do.rownames=!RowNamesAreNull(.local$theFrame), do.colnames=FALSE)
return(TRUE)
#return(TRUE)
} else if (CtrlLetter(keyval, stat, GDK_v) && .local$editable){
dat <- ReadFromClipboard(.local, row.names=1, sep="\t", stringsAsFactors=F)$xx # character vector
cursor.info <- gtkTreeViewGetCursor(widget)
row.idx <- as.integer(gtkTreePathGetIndices(cursor.info$path))+1
if(!is.null(dat)){
if(!length(row.idx)) row.idx <- 1
col.idx <- 1
row.idx <- row.idx[1]
task <- GetTaskPasteIn(.local$theFrame, dat, row.idx, col.idx+COLUMN_OFFSET, do.colnames=F, do.rownames = F)
DoTaskWrapper(.local, task)
}
}
# ignore last row
cursor.info <- gtkTreeViewGetCursor(widget)
if(keyval%in%myValidNavigationKeys){
#cat("*")
if( .local$scrollRowNames) {
.local$scrollRowNames <- FALSE
while(gtkEventsPending())
gtkMainIteration()
gtkPropagateEvent(widget, event)
#gtkTreeSelectionUnselectAll(.local$ss)
gtkAdjustmentSetValue(.local$sw.view.va, gtkAdjustmentGetValue(.local$sw.rn.va))
.local$scrollRowNames <- TRUE
return(TRUE)
}
}
return(FALSE)
}
# Apply command to range [sr, sc]
CommandData <- function(.local, sr, sc) {
command.dialog <- list(
title = "Apply Command",
label = "Apply a command or function to cell selection.\nSelection is stored as variable x.\n",
txt.stringItem = "", label = "Enter Code (Ctrl-Enter To Run)", multi=T, signal.on.startup = F,
signal = c("default", "run.it"),
do.apply.trueFalseItem = FALSE, label = "Apply Function",
signal = c("default", "toggle.sensitive", "apply.over"),
apply.over.radiobuttonItem = c(1,2), item.labels = c("Rows", "Columns"), label = "Over", indent=10,
signal = c("default", function(apply.over, position){
if(get.value(apply.over) == 1) set.value(position, "Right")
if(get.value(apply.over) == 2) set.value(position, "Bottom")
}, "position"),
insert.trueFalseItem = TRUE, label = "Put Output In Table",
signal = c("default", "toggle.sensitive", "position"),
position.radiobuttonItem = c("Replace", "Bottom", value="Right"), label = "Position to Insert", indent=10,
otherTable.stringItem = "", label = "Put Output In Another Table", sensitive=F
)
command <- function(txt, insert, position, do.apply, apply.over, otherTable){
txt <- sub("^=(.*?)$", "\\1", txt, perl=TRUE)
try.short.fn <- paste("function(x) {", txt, "}")
dat <- NULL
.e = new.env()
.e$output <- NULL
x <- as.matrix(.local$theFrame[sr, sc+1, drop=F])
.e$xx <- NULL
tryCatch({
.e$output <- eval(parse(text=txt), envir=.GlobalEnv)
if(!is.function(.e$output)){ # not a function, maybe something like 1:10
.e$xx <- .e$output
if(findxInParseTree(txt) && exists("x", envir = .GlobalEnv))
quick_message(" Warning! A variable called 'x' exists already and will be used. ", win=.local$toplevel)
}
}, error = function(e) { # try putting "function(x)" on the front and see if that works
tryCatch({
.e$output <- eval(parse(text=try.short.fn), envir=.GlobalEnv)
}, error = function(e) quick_message(" Sorry, that didn't make sense to R ^_^;; ", win=.local$toplevel))
})
output <- .e$output
xx <- .e$xx
if(is.function(output)) {
if(do.apply){
xx <- apply(x, apply.over, output)
} else {
xx <- eval(make.call(output, x))
}
}
dat <- NULL
tryCatch({
if(!is.null(xx) && length(xx) && !(is.list(xx) && !is.data.frame(xx))){
if (position == "Replace"){
dat <- array(xx, c(length(sr), length(sc)))
if(is.atomic(dat)) DoTaskWrapper(.local,list(list(func="ChangeCells", args=list(nf=dat, row.idx=sr, col.idx=sc+1))))
} else if (position == "Right"){
dat <- t(ragged.cbind(xx))
new.dat <- array(NA, c(dim(.local$theFrame)[1], dim(dat)[2]))
new.dat[sr, 1:dim(dat)[2]] <- dat
#new.dat <- data.frame(dat, stringsAsFactors=F)
task <- list(list(func="InsertColumns",
arg = list(nf = new.dat, col.idx=max(sc)+1+rep(1, dim(new.dat)[2]))))
DoTaskWrapper(.local, task)
} else if (position == "Bottom") {
dat <- ragged.cbind(xx)
new.dat <- array(NA, c(dim(dat)[1], dim(.local$theFrame)[2]))
new.dat[1:dim(dat)[1], sc+1] <- dat
new.dat <- data.frame(new.dat, stringsAsFactors=FALSE, check.names=FALSE)
task <- list(list(func="InsertRows",
arg = list(nf = new.dat, row.idx=max(sr)+rep(1, dim(new.dat)[1]))))
DoTaskWrapper(.local, task)
}
}
#}, error = function(e) quick_message(" Output couldn't be put into cells ", win=.local$toplevel))
}, error = function(e) cat("Output couldn't be put into cells\n"))
}
run.dialog(func=command, dlg.list=command.dialog, win=.local$toplevel)
}
insert.dialog <- function(.local, row.idx=NULL, col.idx=NULL, insert.type ="Rows", win=NULL) {
choice.list <- list("Columns" = "InsertNAColumns", "Rows" = "InsertNARows")
dialog <- gtkDialog("Insert Rows/Columns", NULL, c("modal", "destroy-with-parent"), "gtk-ok", 1, "gtk-cancel", 0, show = F)
#dialog$setDecorated(FALSE)
do.insert <- function(){
choice.idx <- which(sapply(rev(grb$getGroup()), gtkToggleButtonGetActive))
choice <- names(choice.list)[choice.idx]
func <- choice.list[[choice.idx]]
n <- as.integer(spinner$getValue())
stopifnot(is.integer(n) && n > 0)
arg <- list()
if(choice == "Rows"){
idx <- row.idx
stopifnot(!is.null(idx))
theIdx <- rep(idx+1, n)
arg$row.idx <- theIdx
} else {
idx <- col.idx
stopifnot(!is.null(idx))
theIdx <- rep(idx+1, n)
arg$col.idx <- theIdx
}
DoTaskWrapper(.local,list(list(func=func,arg=arg)))
}
box0 <- gtkVBoxNew(FALSE, 5)
grb.keypress <- function(obj, evt){
keyval <- evt[["keyval"]]
if(keyval == GDK_Return){
do.insert()
dialog$destroy()
.local$view$grabFocus()
return(TRUE)
}
if(keyval == GDK_Right){
spinner$spin(GtkSpinType["step-forward"], 1)
return(TRUE)
}
if(keyval == GDK_Left){
spinner$spin(GtkSpinType["step-backward"], 1)
return(TRUE)
}
FALSE
}
fr2 <- gtkFrameNew(label="Insert")
box4 <- gtkVBoxNew(FALSE, 5)
grb <- gtkRadioButtonNewWithLabel(NULL, label=names(choice.list)[1])
gSignalConnect(grb, "key-press-event", grb.keypress)
grb$setActive(TRUE)
box4$packStart(grb, FALSE, FALSE, 0)
grb <- gtkRadioButtonNewWithLabel(group=grb$getGroup(), label=names(choice.list)[2])
gSignalConnect(grb, "key-press-event", grb.keypress)
if(!is.null(insert.type) && insert.type==names(choice.list)[2]) grb$setActive(TRUE)
box4$packStart(grb, FALSE, FALSE, 0)
fr2$add(box4)
box0$add(fr2)
box1 <- gtkHBoxNew(FALSE, 5)
box1$packStart(gtkLabelNew("Number To Insert"), FALSE, FALSE, 10)
spinner_adj <- gtkAdjustment(1, 1, .Machine$integer.max, 1, 10.0)
spinner <- gtkSpinButton(spinner_adj, 1.0, 0)
spinner$setValue(1)
box1$add(spinner)
spinner$grabFocus()
box0$add(box1)
gSignalConnect(spinner, "key-press-event", function(obj, evt){
if(evt[["keyval"]] == GDK_Return) {
spinner$update()
do.insert()
dialog$destroy()
}
if(evt[["keyval"]] == GDK_Escape) {
dialog$destroy()
}
.local$view$grabFocus()
FALSE
})
if(!is.null(win)) {
checkPtrType(win, "GtkWindow")
dialog$setPosition(GtkWindowPosition["center-on-parent"])
dialog$setTransientFor(win)
}
gSignalConnect(dialog, "response", function(dlg, arg1, ...) {
if(arg1==1) do.insert()
dialog$destroy()
.local$view$grabFocus()
})
dialog[["vbox"]]$packStart(box0, TRUE, TRUE, 10)
dialog$showAll()
}
DeleteSelection <- function(.local){
selections <- .local$selections
if(length(selections)){
xors <- get.xor.selection(.local$selections)
# drop rows and cols that don't contain cells
xbb <- as.integer(c(
rownames(xors)[c(1, nrow(xors))],
colnames(xors)[c(1, ncol(xors))] ))
row.idx <- xbb[1]:xbb[2]
col.idx <- xbb[3]:xbb[4]+COLUMN_OFFSET
theSelection <- .local$theFrame[row.idx,
col.idx, drop=FALSE]
theSelection[xors] <- NA
nf <- theSelection
task <- list(list(func="ChangeCells",
arg = list(nf=nf, row.idx=row.idx, col.idx=col.idx)))
DoTaskWrapper(.local, task)
}
}
GetCursorRowColIdx <- function(widget){
cursor.info <- gtkTreeViewGetCursor(widget)
path <- cursor.info$path
if(is.null(path)) return(NULL)
col.idx <- GetColIdx(cursor.info$focus.column)
row.idx <- as.integer(gtkTreePathGetIndices(path))+1
return(c(row.idx, col.idx))
}
ViewKeyPress <- function(widget, event, data) {
.local <- data
retval <- TRUE
event.time <- event[["time"]]
keyval <- event[["keyval"]]
.local$last.view.event <- event
.local$last.key.pressed <- keyval
# remove click info
.local$last_click_info <- NULL
#cat(c(".", "*")[.local$allow.key.flag+1])
if(0 && !is.null(.local$entry)) {
#print("ViewKeyPress unrealizing entry")
.local$entry$unrealize()
.local$entry <- NULL
}
if(.local$allow.key.flag && event.time > .local$last.time && is.null(.local$entry)){ #4-21-10
#if( allow.key.flag && event.time > .local$last.time){
# if(.local$allow.key.flag) if(!gtkWidgetIsFocus(.local$view)) { #added 4-21-2010
# print("ViewKeyPress: focus is FALSE")
# gtkWidgetGrabFocus(.local$toplevel)
# gtkWidgetGrabFocus(.local$view)
# }
#tryCatch({
.local$last.time <- event.time
.local$allow.key.flag <- FALSE
stat <- event[["state"]]
allColumns <- .local$allColumns
view <- .local$view
model <- .local$model
# Paging events
if (CtrlLetter(keyval, stat, GDK_c) || CtrlLetter(keyval, stat, GDK_C)) {
namesflag <- CtrlLetter(keyval, stat, GDK_C)
gsr <- GetSelectedRows(.local$view, .local)
# block transient interactions with a popup
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
nf <- GetSelectedCells(.local)
if(!is.null(nf))
CopyToClipboard(nf,
do.rownames=namesflag && !RowNamesAreNull(.local$theFrame), do.colnames=namesflag && !ColNamesAreNull(.local$theFrame))
retval <- TRUE
.local$allow.key.flag <- TRUE
#return(TRUE)
} else if (CtrlLetter(keyval, stat, GDK_v) && .local$editable){
# print("Ctrl-v")
ReadFromClipboardWrapper(.local)
retval <- TRUE
.local$allow.key.flag <- TRUE
#return(TRUE)
} else if (CtrlLetter(keyval, stat, GDK_z)){
#print("Ctrl-z")
DoUndo(.local)
.local$allow.key.flag <- TRUE
retval <- TRUE
#return(TRUE)
} else if (CtrlLetter(keyval, stat, GDK_a)){
#print("Ctrl-a")
SelectAll(.local)
.local$allow.key.flag <- TRUE
retval <- TRUE
#return(TRUE)
} else if(keyval == GDK_Delete && .local$editable) {
# replace set of selection rectangles with disjoint
# rectangles in order to minimize paint/display properly
DeleteSelection(.local)
.local$allow.key.flag <- TRUE
retval <- TRUE
#return(TRUE)
} else if(keyval%in%myMetaKeys || (stat == GdkModifierType['control-mask'])){
# start shift-selection if user pressed shift
if(keyval%in%myShiftKeys){
rc <- GetCursorRowColIdx(widget)
if(!is.null(rc)){
.local$selections <- list(list(start=c(row.idx=rc[1], col.idx=rc[2])))
#print("Shift pressed")
#print(unlist(.local$selections))
}
}
.local$allow.key.flag <- TRUE
retval <- TRUE
#return(TRUE)
} else if (keyval%in%myValidNavigationKeys){
#.local$rectangles <- list()
if (keyval == GDK_Up || ShiftLetter(keyval, stat, GDK_Return))
moveDirection <- "up"
else if (keyval %in% c(GDK_Down, GDK_Return))
moveDirection <- "down"
else if(keyval %in% c(GDK_Right, GDK_Tab))
moveDirection <- "right"
else if(keyval %in% c(GDK_Left, GDK_ISO_Left_Tab))
moveDirection <- "left"
else if(keyval %in% c(GDK_Page_Down))
moveDirection <- "page_down"
else if(keyval %in% c(GDK_Page_Up))
moveDirection <- "page_up"
else if(keyval %in% c(GDK_Home))
moveDirection <- "home"
else if(keyval %in% c(GDK_End))
moveDirection <- "end"
else
moveDirection <- NULL
if(!is.null(moveDirection)){
MoveCursor(widget, moveDirection, .local, stat)
retval <- TRUE
} else {
retval <- FALSE
}
.local$allow.key.flag <- TRUE
} else if (keyval==GDK_Insert){
if(.local$editable){
#print("Starting insert")
cursor.info <- gtkTreeViewGetCursor(widget)
path <- cursor.info$path
col.idx <- GetColIdx(cursor.info$focus.column)
row.idx <- as.integer(gtkTreePathGetIndices(path))
if(!is.null(path)){
insert.dialog(.local, row.idx, col.idx, insert.type="Rows", win=.local$toplevel)
}
}
.local$allow.key.flag <- TRUE
retval <- TRUE
} else if (keyval == GDK_equal && length(sc <- GetSelectedColumns(.local))) {
if(length(sr <- GetSelectedRows(.local$view, .local))==0)
sr <- 1:(dim(.local$theFrame)[1]-EXTRA_ROW)
CommandData(.local, sr, sc)
.local$allow.key.flag <- TRUE
retvale <- TRUE
} else if (keyval%in%myValidInputKeys){ # valid input keys
cursor.info <- gtkTreeViewGetCursor(widget)
col.idx <- GetColIdx(cursor.info$focus.column)
renderer <- allColumns[[col.idx]]$renderer
path <- cursor.info$path
row.idx <- as.integer(gtkTreePathGetIndices(path))+1
ddf <- dim(.local$theFrame)
# ignore last row
if(row.idx > ddf[1]-EXTRA_ROW || col.idx > ddf[2]-2) {
.local$allow.key.flag <- TRUE
retval <- TRUE
} else {
if(.local$editable){
gtkTreeViewSetCursorOnCell(widget, cursor.info$path,
cursor.info$focus.column, renderer, TRUE)
if(keyval != GDK_space){
gtkPropagateEvent(view, event)
}
} else {
.local$allow.key.flag <- TRUE
}
retval <- FALSE
# Bug spotted by Liviu 28/9/12
if(keyval == GDK_space){
retval <- TRUE
}
} # if row.idx
#.local$allow.key.flag <- TRUE
}
#}, error = function(e){
# warning(e)
# #cat("E")
# .local$allow.key.flag <- TRUE
# }) # TryCatch
} else {# if allow.key.flag is false or event.time
#print("ViewKeyPress Blocked")
}
if(!keyval%in%myValidKeys) { # otherwise bad things
.local$allow.key.flag <- TRUE
}
return(retval)
}
GetSelectedColumns <- function(.local, restrict=TRUE) {
rv <- .local$selectedColumns
if(restrict) rv <- rv[!rv%in%(dim(.local$theFrame)[2]-1)]
return(rv)
}
RendererEditingStarted <- function(renderer, entry, path, data) {
.local <- data$.local
#print(paste("RendererEditingStarted called, local key flag is", .local$allow.key.flag))
if(!is.null(.local$entry)) {
#cat ("Already using entry!!!\n")
}
.local$entry <- entry
checkPtrType(entry, "GtkEntry")
col.idx <- data$col.idx
#print(entry)
theFrame <- .local$theFrame
view <- .local$view
# don't paint rectangles
.local$do.paint <- FALSE
myBlack = as.GdkColor("black")
myWhite = as.GdkColor("white")
gtkEntrySetHasFrame(entry, TRUE)
gtkWidgetModifyBase(entry, as.integer(1), myBlack)
gtkWidgetModifyBase(entry, as.integer(3), myBlack)
gtkWidgetModifyText(entry, as.integer(1), myWhite)
gtkWidgetModifyText(entry, as.integer(3), myWhite)
#entry$setAlignment(1)
gtkWidgetSetEvents(entry, GdkEventMask["all-events-mask"])
gObjectSetData(entry, "index", col.idx)
gObjectSetData(entry, "renderer", renderer)
gObjectSetData(entry, "path.str", path)
gObjectSetData(entry, "path", gtkTreePathNewFromString(path))
gObjectSetData(entry, "text", gtkEntryGetText(entry))
# if the used pressed space, we want to insert it the normal way
# this is surprisingly hard to do with a treeview...
# However we could probably avoid the propagate call
# this way!
if(identical(.local$last.key.pressed, GDK_space))
gSignalConnect(entry, "map-event",
function(entry, event, data=.local) {
entry$setText(" ")
#entry$setText(.local$last.key.pressed)
gtkEditableSelectRegion(entry, 1, 1)
})
# Insert cursor at point where we clicked
gSignalConnect(entry, "map-event", function(entry, event, data=.local){
#print("RendererEditingStarted: map-event insert")
# This denies focus to the entry
if(0){
insertion_point <- 0
pixel_size <- entry$getLayout()$getPixelSize()
click_info <- .local$last_click_info
col_width <- click_info$column$getWidth()
text_width <- pixel_size$width
text_pos <- (click_info$cell.x - col_width + text_width)/(text_width)
if(length(text_pos) != 1 || text_pos < 0 || text_pos > 1) text_pos <- 0
insertion_point <- round(text_pos*nchar(entry$getText()))
gtkEditableSelectRegion(entry, insertion_point, insertion_point)
}
#print("RendererEditingStarted: map-event insert returning")
return(FALSE)
})
# 7-4-10 - disable right-click menu, seems to confuse people
IgnoreRClick <- function(entry, event, data){
if(event[["button"]] == as.integer(3)) return(TRUE)
return(FALSE)
}
if(1) gSignalConnect(entry, "focus-in-event", function(entry, event) {
#print("RendererEditingStarted: entry focused in")
return(FALSE)
})
gSignalConnect(entry, "button-press-event", IgnoreRClick)
gSignalConnect(entry, "button-release-event", IgnoreRClick)
# We've trapped the temporary GtkEntry the renderer creates.
gSignalConnect(entry, "key-press-event", RendererEntryKeyPress, data=.local)
# you can leave the entry in 2 ways, focusing out or pressing a key from within
.local$entry.focus.out <- gSignalConnect(entry, "unrealize", function(entry, ...){
#print("RendererEditingStarted: entry focused out")
#if(!identical(.local$ignore_entry_focus_out, NULL)) {
# .local$ignore_entry_focus_out <- NULL
# return(FALSE)
#}
#.local$ignore_entry_focus_out <- TRUE
#gtkPropagateEvent(entry, event)
EnterTextIntoCell(entry, .local)
#print(.local$entry)
.local$allow.key.flag <- TRUE # unlock
.local$entry <- NULL
# changed 082210
.local$entry.focus.out <- NULL
#print("RendererEditingStarted: unlocked from focus-out")
return(FALSE)
}) # focus out event
.local$doingEntryIntoCompletion <- FALSE
typ <- GetClasses(.local$theFrame)[col.idx]
if(typ == "factor"){
create.completion.model <- function(factor.levels) {
store <- gtkListStoreNew("gchararray")
sapply(factor.levels, function(x) store$set(store$append()$iter, 0, x))
return(store)
}
factor.levels <- levels(theFrame[,col.idx])
completion <- gtkEntryCompletionNew()
# Assign the completion to the entry
# Create a tree model and use it as the completion model
completion.model <- create.completion.model(factor.levels)
entry$setData("levels", factor.levels)
completion$setModel(completion.model)
completion$setTextColumn(0)
completion$setMinimumKeyLength(0)
completion$setInlineCompletion(TRUE)
completion$setPopupSingleMatch(FALSE)
.local$doingEntryIntoCompletion <- TRUE
# move cursor down if you hit return after you select a match
gSignalConnect(completion, "match-selected", after=F, function(widget, completion.model, iter){
#entry$setText(gtkTreeModelGetValue(completion.model, iter, 0)$value)
#print("RendererEditingStarted: Focused out via match-selection")
EnterTextIntoCell(entry, .local)
MoveCursor(.local$view, "down", .local)
return(FALSE)
})
entry$setCompletion(completion)
entry$setData("completion", completion)
entry$setData("pos", 0) # match position
entry$setData("current.match", character(0))
}
#print("RendererEditingStarted returning")
return(FALSE)
}
EnterTextIntoCell <- function(entry, .local){
#w <- TransientWindow("Updating...", .local)
#on.exit(w$destroy())
#print("EnterTextIntoCell entered")
col.idx <- gObjectGetData(entry,"index")
row.idx <- as.integer(gObjectGetData(entry, "path.str"))+1
keyval <- gObjectGetData(entry,"keyval")
#} else {
theText <- gtkEntryGetText(entry)
#}
#print(c("theText", theText))
nf <- data.frame(theText, stringsAsFactors=F, check.names=F)
task <- list(list(func="ChangeCells",
arg = list(nf=nf, row.idx=row.idx, col.idx=col.idx)))
DoTaskWrapper(.local, task)
#print("EnterTextIntoCell returning")
}
RendererEntryKeyPress <- function(entry, event, data) {
.local <- data
retval <- TRUE
event.time <- event[["time"]]
keyval <- event[["keyval"]]
#cat(c("A", "B")[.local$allow.renderer.key.flag+1])
if(1){# .local$allow.renderer.key.flag && event.time >= .local$last.time){
.local$last.time <- event.time
retval <- FALSE
keyval <- event[["keyval"]]
stat <- as.flag(event[["state"]])
gObjectSetData(entry, "keyval", keyval)
gObjectSetData(entry,"stat", stat)
view <- .local$view
# control keys for popup selection
if(.local$doingEntryIntoCompletion) { # we're in the popup. or something.
retval <- FALSE
if (keyval %in% c(GDK_Return)){
# this is to handle returns on the entry completion
current.match <- entry$getData("current.match")
if(length(current.match))
entry$setText(current.match)
if(nchar(entry$getText()) > 0) {
#EnterTextIntoCell(entry, .local)
#.local$allow.key.flag <- TRUE # unlock
#.local$entry <- NULL
#print("RendererEntryKeyPress, returning on completion")
MoveCursor(view, "down", .local)
}
#.local$allow.key.flag <- TRUE # unlock
#.local$entry <- NULL
#
retval <- FALSE
} else if(0 && keyval == GDK_Escape){
#entry$setText(entry$getData("text"))
#MoveCursor(view, "down", .local)
retval <- TRUE
}
# Keep track of entry completion
# this is really hacky. first, get the matches in the entry
ll <- entry$getData("levels")
# this may not be perfect...
matches <- ll[regexpr(tolower(entry$getText()),tolower(ll)) == 1]
if( length(matches) > 1) { # because the popup is only for these matches
# now keep track of the up/down arrow selection...
if (keyval %in% c(GDK_Up, GDK_Down)){
xx <- -1
if(keyval%in%c(GDK_Down))
xx <- 1
xx <- (entry$getData("pos") + xx)%%(length(matches)+1)
} else {
xx <- 0
}
#print(xx)
#print(matches[xx])
entry$setData("pos", xx)
entry$setData("current.match", matches[xx])
}
# just a regular text cell
# calling MoveCursor ends the focus of the entry
} else if (!keyval%in%myValidInputKeys && !keyval%in%myMetaKeys){
# EnterTextIntoCell(entry, .local) # update the cell
# .local$allow.key.flag <- TRUE # unlock
# .local$entry <- NULL
if (keyval == GDK_Escape){
gtkEntrySetText(entry, gObjectGetData(entry, "text"))
} else if (keyval == GDK_Up || ShiftLetter(keyval, stat, GDK_Return)){
MoveCursor(view, "up", .local)
} else if ( keyval == GDK_Return || keyval == GDK_Down ) {
MoveCursor(view, "down", .local)
} else if (keyval == GDK_Left || keyval == GDK_ISO_Left_Tab) {
MoveCursor(view, "left", .local)
} else if (keyval == GDK_Right || keyval == GDK_Tab) {
MoveCursor(view, "right", .local)
}
retval <- FALSE
} else { # not entry into completion, or invalid keys
}
if(keyval%in%myMetaKeys) # ignore these
retval <- TRUE
}
# new code here
#print("RendererEntryKeyPress setting flag to TRUE")
#.local$allow.key.flag <- TRUE
#print(.local$view$isFocus()) # Here's the problem....
return(retval)
}
ColumnSetState <- function(allColumns, ii, state, .local){
if(length(ii) != 1 || ii < 1 || length(allColumns) < ii)
stop(paste("trying to set column index outside bounds:", ii))
#if(ii != length(allColumns))
gtkWidgetSetState(allColumns[[ii]]$button,
ifelse(state, as.integer(1), as.integer(0)))
.local$allColumns <- allColumns
if(state) {
.local$selectedColumns <- sort(union(ii, .local$selectedColumns))
} else {
.local$selectedColumns <- sort(setdiff(.local$selectedColumns, ii))
}
}
UpdateColumnSelection <- function(.local, selectedColumns.new) {
allColumns <- .local$allColumns
selectedColumns <- GetSelectedColumns(.local, restrict=FALSE)
ll <- length(allColumns)
if(ll > 1){ # for zero columns, don't select anything
# Don't activate the last column - it's left blank
for (ii in setdiff(selectedColumns.new, selectedColumns))
ColumnSetState(allColumns, ii, TRUE, .local)
for (ii in setdiff(selectedColumns, selectedColumns.new))
ColumnSetState(allColumns, ii, FALSE, .local)
}
}
SelectAll <- function(.local){
allColumns <- .local$allColumns
UpdateColumnSelection(.local, 1:length(allColumns))
if(length(.local$rectangles)){
.local$rectangles <- list()
.local$viewGetBinWindow$invalidateRect(NULL, FALSE)
}
.local$do.paint <- TRUE
# block transient interactions with a popup
w <- TransientWindow("Selecting...", .local)
on.exit(w$destroy())
#gtkTreeSelectionSelectAll(gtkTreeViewGetSelection(.local$view))
.local$selections <- list(list(
start=c(row.idx=1, col.idx=1),
end=c(row.idx=nrow(.local$theFrame), col.idx=ncol(.local$theFrame)-2)
))
UpdateSelectionRectangle(.local)
}
RowNamesButtonPress <- function(widget, event, data) {
.local <- data
# kill any open entries
typ <- event[['type']]
stat <- event[['state']]
info <- widget$getPathAtPos(event[["x"]], event[["y"]])
if(is.null(info$path)) return(TRUE)
row.idx <- info$path$getIndices()+1
col.idx <- 1
allColumns <- .local$allColumns
#UpdateColumnSelection(.local, 1:(length(allColumns)))
.local$do.paint <- TRUE
#UpdateColumnSelection(.local, integer(0))
#if(length(.local$rectangles)){
# .local$rectangles <- list()
# .local$viewGetBinWindow$invalidateRect(NULL, FALSE)
#}
if (.local$scrollRowNames) # i.e., we're not propagating event
if (event[["button"]] == as.integer(1)){
if(row.idx > dim(.local$theFrame)[1]-EXTRA_ROW) return(TRUE)
if(typ == as.integer(4)){ # single clicked
selections <- .local$selections
#if(0){
theFrameNCols <- ncol(.local$theFrame)-1-COLUMN_OFFSET
rowSel <- list(start=c(row.idx=row.idx, col.idx=1),
end=c(row.idx=row.idx, col.idx=theFrameNCols))
# selectedRows <- GetSelectedRows(.local)
if (as.flag(stat) & GdkModifierType['shift-mask']) { # range
if(is.null(selections) || !length(selections))
selections <- list(list(start=rowSel$start))
# For the tricky case where we've selected a cell range
# and shift-click - unsure of best behavior here
selections[[length(selections)]]$start['col.idx'] <- 1
selections[[length(selections)]]$end <- rowSel$end
} else if (as.flag(stat) & GdkModifierType['control-mask']) { # range
# Simpler: Just add the column to whatever you clicked.
selections <- .local$selections
if(length(selections)){
xors <- get.xor.selection(selections)
airx <- as.integer(rownames(xors))
selectedRows <- airx[rowSums(xors)>0]
# if the column index is not in the range
# of selected columns,
# we want to add it
whichidx <- which(airx==row.idx)
# if the entire column is already selected and TRUE
# then set it to FALSE
if(row.idx%in%selectedRows){
#dont.add.flag <- TRUE
if(all(xors[whichidx,])
&& ncol(xors)==theFrameNCols){
xors[whichidx,] <- FALSE
selections <- get.xor.rectangles(xors)
} else {
xors[whichidx,] <- FALSE
selections <- get.xor.rectangles(xors)
selections[[length(selections)+1]] <- rowSel
}
# otherwise set the whole column to TRUE
} else {
selections[[length(selections)+1]] <- rowSel
}
# if length(selections)
} else {
selections[[length(selections)+1]] <- rowSel
}
} else {
gtkTreeSelectionUnselectAll(.local$ss)
selections <- list()
selections[[length(selections)+1]] <- rowSel
}
.local$selections <- selections
#} # if 0
.local$do.paint <- TRUE
UpdateSelectionRectangle(.local)
gdkWindowInvalidateRect(.local$viewGetBinWindow, NULL, FALSE)
#gdkWindowInvalidateRect(.local$view.rnGetBinWindow, NULL, FALSE)
# prevent a repeated click from editing
cursor.info <- gtkTreeViewGetCursor(widget)
if(is.null(cursor.info$path))
return(FALSE)
cursor.row.idx <- as.integer(gtkTreePathGetIndices(cursor.info$path))+1
if(identical(cursor.row.idx, row.idx))
return(TRUE)
DoUserCall("RowClicked", list(idx = info$path$getIndices()+1), .local)
# scroll main view in sync
if( .local$scrollRowNames) {
.local$scrollRowNames <- FALSE
gtkPropagateEvent(widget, event)
gtkTreeSelectionUnselectAll(.local$ss)
gtkAdjustmentSetValue(.local$sw.view.va, gtkAdjustmentGetValue(.local$sw.rn.va))
.local$scrollRowNames <- TRUE
return(TRUE)
}
} else if(typ == as.integer(5)){ # ignore double click
return(TRUE)
}
} else if (event[["button"]] == as.integer(3)){ # our popup menu
m <- Row3rdButtonMenu(.local, row.idx)
gtkMenuPopupHack(m, button = event$GetButton(),
activate.time = gdkEventGetTime(event))
return(TRUE)
}
return(FALSE)
}
# given the selection, creates a bounding box mask 041210
get.xor.selection <- function(selections){
mtx <- apply(as.data.frame(selections), 1, range)
stopifnot(nrow(mtx)==2 && ncol(mtx)> 0 && ncol(mtx)%%2==0)
ary <- array(FALSE, apply(mtx, 2, diff)+1)
msq <- function(xc) {xc <- unlist(xc); seq.int(from=xc[1], to=xc[2])}
dimnames(ary) <- list(msq(mtx[,1]), msq(mtx[,2]))
for(ii in seq(length=length(selections))){
x1 = as.data.frame(selections[[ii]])
x1.idx <- x1 - mtx[1,] + 1
if(!(nrow(x1.idx)==2 && ncol(x1.idx)==2)) break;
sii <- msq(x1.idx[1,])
sjj <- msq(x1.idx[2,])
ary[sii, sjj] <- !ary[sii, sjj]
}
return(ary)
}
get.xor.rectangles <- function(xors){
rects = list()
row.offset <- as.integer(rownames(xors)[1])-1
names(row.offset)<-NULL
col.offset <- as.integer(colnames(xors)[1])-1
names(col.offset)<-NULL
while(any(xors)){
wxors <- which(xors, arr.ind=T)
# get first element which isn't in first row containing true
top.row = wxors[1,1]
left.col = wxors[1,2]
first.col.containing.true <- xors[top.row:nrow(xors),left.col]
row.height <- which(diff(first.col.containing.true)!=0)[1]
if(is.na(row.height)) row.height <- length(first.col.containing.true)
bottom.row <- top.row+row.height-1
xx <- colSums(!xors[top.row:bottom.row,,drop=F])
right.col <- which(xx[-(1:left.col)]!=0)[1] + left.col-1
if(is.na(right.col)) right.col <- length(xx)
stopifnot(all(xors[top.row:bottom.row, left.col:right.col]))
xors[top.row:bottom.row, left.col:right.col] <- FALSE
names(top.row) <- names(left.col) <- names(bottom.row) <- names(right.col) <- NULL
rects[[length(rects)+1]] <- list(
start = c(row.idx=top.row+row.offset,
col.idx=left.col+col.offset),
end = c(row.idx=bottom.row+row.offset,
col.idx=right.col+col.offset)
)
#print("A")
#print(rects[[length(rects)]])
#print("B")
}
return(rects)
}
# Takes selections as xor-ing one another
# Return selection list, i.e. list of start, end row.idx col.idx
GetSelectedCells <- function(.local){
# replace set of selection rectangles with disjoint
# rectangles in order to minimize paint/display properly
selections <- .local$selections
if(length(selections)){
xors <- get.xor.selection(.local$selections)
# drop rows and cols that don't contain cells
xbb <- as.integer(c(
rownames(xors)[c(1, nrow(xors))],
colnames(xors)[c(1, ncol(xors))] ))
wxx <- xbb[3:4] == ncol(.local$theFrame)-1
theSelection <- NULL
if(!all(wxx)){
xbb[3:4][wxx] <- ncol(.local$theFrame)-2
theSelection <- .local$theFrame[xbb[1]:xbb[2],
xbb[3]:xbb[4]+COLUMN_OFFSET, drop=FALSE]
# remove entirely unselected rows and columns
if(any(rowSums(xors)==0)){
theSelection <- theSelection[rowSums(xors)!=0,,drop=F]
xors <- xors[rowSums(xors)!=0,,drop=F]
}
if(any(colSums(xors)==0)){
theSelection <- theSelection[,colSums(xors)!=0,drop=F]
xors <- xors[,colSums(xors)!=0,drop=F]
}
if(!all(xors)){
theSelection[!xors] <- NA
}
}
}
return(theSelection)
}
ViewButtonPress <- function(widget, event, data) {
.local <- data
.local$last.view.event <- event
model <- .local$model
allColumns <- .local$allColumns
info <- widget$getPathAtPos(event[["x"]], event[["y"]])
if (is.null(info$column)) return(FALSE)
renderer <- info$column$getCellRenderers()[[1]]
# store last click info here
.local$last_click_info <- info
if(is.null(info$path)) return(TRUE)
row.idx <- info$path$getIndices()+1
col.idx <- GetColIdx(info$column)
typ <- event[['type']]
stat <- event[['state']]
if (event[["button"]] == as.integer(3)){ # our popup menu
m <- Cell3rdButtonMenu(.local, row.idx, col.idx)
gtkMenuPopupHack(m, button = event$GetButton(),
activate.time = gdkEventGetTime(event))
return(TRUE)
} else if (event[["button"]] == as.integer(1)){
if(typ == as.integer(4)){ # single clicked
# we want to keep going if user clicks on column to resize - hard to tell
if(is.null(info$path) || (identical(row.idx, 1) && info$cell.x < 5)) {
.local$rectangles <- list()
return(FALSE)
}
# if it isn't editable,
# make sure we don't drop into edit mode
if(!.local$editable) {
cursor.info <- gtkTreeViewGetCursor(widget)
path <- cursor.info$path
if(is.null(path)){
cursor.row.idx = 1
cursor.col.idx = 2
} else {
cursor.row.idx <- as.integer(gtkTreePathGetIndices(path))+1
cursor.col.idx <- GetColIdx(cursor.info$focus.column)
}
if(identical(cursor.row.idx, row.idx) && identical(col.idx, cursor.col.idx)){
return(TRUE)
}
}
selectedColumns <- GetSelectedColumns(.local)
if (as.flag(stat) & GdkModifierType['shift-mask']) { # range
if(is.null(.local$start.column.select)) .local$start.column.select <- col.idx
selectedColumns <- .local$start.column.select:col.idx
if(is.null(.local$selections)) return(TRUE)
stopifnot(length(.local$selections[[length(.local$selections)]])>0)
.local$selections[[length(.local$selections)]]$end <-
c(row.idx=row.idx, col.idx=col.idx)
# If SHIFT is held down and no start is selected, do nothing
if(is.null(.local$start.column.select)) {
return(TRUE)
# .local$selections <- list()
# .local$selections[[length(.local$selections)+1]] <-
# list(start=c(row=row.idx, col=col.idx))
}
} else if (as.flag(stat) & GdkModifierType['control-mask']) { # range
#cursor.row.idx <- as.integer(gtkTreePathGetIndices(path))+1
selectedColumns <- union(selectedColumns, col.idx)
#selectedColumns <-
# c(union, setdiff)[[(col.idx%in%selectedColumns+COLUMN_OFFSET)]](selectedColumns, col.idx)
if(is.null(.local$start.column.select))
.local$start.column.select <- col.idx
# Treat ctrl-button down as starting a new selection range
.local$selections[[length(.local$selections)+1]] <- list(start=
c(row.idx=row.idx, col.idx=col.idx))
return(TRUE)
} else { # no control or shift
selectedColumns <- col.idx
.local$start.column.select <- col.idx
.local$selections <- list()
.local$selections[[1]] <- list()
.local$selections[[1]]$start <- c(row.idx=row.idx, col.idx=col.idx)
#gsr <- GetSelectedRows(.local$view, .local)
gsc <- GetSelectedColumns(.local)
# This is to prevent clicking twice on the same row from opening
# an edit entry in a different cell
# if(length(gsr) && length(gsc) && gsr[1] == row.idx && (length(gsr) > 1 || length(gsc) > 1 || col.idx != gsc) ) {
# if you've clicked twice on a selected cell,
# ignore it
# Test whether the first selected row is equal to the row index
mysr <- gtkTreeSelectionGetSelectedRows(.local$ss)$retval
if(length(mysr)){
first.row.idx <- gtkTreePathGetIndices(mysr[[1]])+1
if(identical(first.row.idx, row.idx) &&
!(col.idx%in%gsc) ) {
if(!is.null(.local$entry)) {
.local$entry$unrealize()
.local$entry <- NULL
}
.local$do.paint <- TRUE
.local$rectangles <- list()
.local$view$setCursorOnCell(info$path, info$column, renderer, FALSE)
UpdateColumnSelection(.local, selectedColumns)
UpdateSelectionRectangle(.local)
return(TRUE)
}
}
}
UpdateColumnSelection(.local, selectedColumns)
} else if (typ == as.integer(5)) { # ignore dclick
return(TRUE)
}
}
ddf <- dim(.local$theFrame)
# reset the cursor so it's visible
#.local$flash.cursor <- TRUE
# Flash the rectangles off when you click
.local$do.paint <- TRUE # 2-12-10
if(length(.local$rectangles)){
# print("resetting")
#.local$rectangles <- list() # 3-2-10
widget$getBinWindow()$invalidateRect(NULL, FALSE) # 041012
}
return(FALSE)
}
# Update rectangle from .local$view selected rows.
# Don't draw if only 1 selected square
# Don't update if nothing changed.
UpdateSelectionRectangle <- function(.local, selection=NULL, widget=.local$view){
allColumns <- .local$allColumns
sw.va <- .local$sw.view.va
ncolmax <- ncol(.local$theFrame)
path2.index <- path1.index <- integer(0)
row.idx <- col.idx <- NULL
selections <- .local$selections
t.cols <- integer(0)
t.rows <- integer(0)
if(length(selections) > 1){
xors <- get.xor.selection(.local$selections)
selections <- get.xor.rectangles(xors)
t.rows <- as.integer(rownames(xors)[rowSums(xors)>0])
t.cols <- as.integer(colnames(xors)[colSums(xors)>0])
} else if (length(selections) == 1) {
ls1 <- .local$selections[[1]]
t.seq <- sort(c(ls1$start['row.idx'], ls1$end['row.idx']))
t.from <- t.seq[1]
t.to <- t.seq[2]
if(!is.na(t.from) && !is.na(t.to)){
t.rows <- seq(from=t.from, to=t.to)
t.cols <- seq(from=.local$selections[[1]]$start['col.idx'],
to =.local$selections[[1]]$end['col.idx'])
}
}
UpdateColumnSelection(.local, t.cols)
# row name rectangles
if(0 && length(t.rows)){
view.rn <- .local$view.rn
va.value <- gtkAdjustmentGetValue(.local$sw.view.va)
t.blocks <- MergeAdjacent(t.rows)-1
rn.rectangles <- vector("list", nrow(t.blocks))
rn.col <- .local$rowname.column.obj$column
for(ii in seq(length=nrow(t.blocks))){
path.start <- gtkTreePathNewFromString(t.blocks[ii,1])
path.end <-
gtkTreePathNewFromString(t.blocks[ii,2])
rn.rect.start <-
gtkTreeViewGetCellArea(view.rn, path.start, rn.col)$rect
rn.rect.end <-
gtkTreeViewGetCellArea(view.rn, path.end, rn.col)$rect
rn.rect <- gdkRectangleUnion(rn.rect.start, rn.rect.end)$dest
rn.rect$y <- rn.rect$y + va.value # adjust for scroll
rn.rectangles[[ii]] <- rn.rect
}
.local$rn.rectangles <- rn.rectangles
}
if(length(selections)){
va.value <- gtkAdjustmentGetValue(.local$sw.view.va)
view <- .local$view
rectangles <- list()
for(ii in seq.int(length.out=length(selections))){
sel.rect <- selections[[ii]]
if(length(sel.rect$start) < 2 || length(sel.rect$end) < 2) break;
row.block <- sort(c(sel.rect$start['row.idx'], sel.rect$end['row.idx']))
row.block.start <- row.block[1]
path.start <- gtkTreePathNewFromString(row.block.start-1)
row.block.end <- row.block[2]
if(row.block.start == row.block.end)
path.end <- path.start
else
path.end <- gtkTreePathNewFromString(row.block.end-1)
col.block <- sort(c(sel.rect$start['col.idx'], sel.rect$end['col.idx']))
col.block.start <- col.block[1]
col.start <- allColumns[[col.block.start]]$column
rect <- gtkTreeViewGetCellArea(view, path.start, col.start)$rect
col.block.end <- col.block[2]
if(col.block.start != col.block.end || row.block.end != row.block.start){
col.end <- allColumns[[col.block.end]]$column
rect.end <- gtkTreeViewGetCellArea(view, path.end, col.end)$rect
rect <- gdkRectangleUnion(rect, rect.end)$dest
}
rect$y <- rect$y + va.value # adjust for scroll
rectangles[[length(rectangles) + 1]] <- rect
}
.local$rectangles <- rectangles
} else { # if length(...)
.local$rectangles <- list()
}
DoUserCall("Selection", list(selections=selections), .local)
gdkWindowInvalidateRect(.local$viewGetBinWindow, NULL, FALSE)
return(FALSE)
}
ViewButtonRelease <- function(widget, event, data){
#if(.local$do.rendererEditingStarted){ # if we've started editing, don't do anything
.local <- data
.local$last.view.event <- event
typ <- event[['type']]
stat <- event[['state']]
row.idx <- col.idx <- integer(0)
# ignore this if there's an entry in progress
if(is.null(.local$entry)){ # 041012 removed (if (1 || is.null ...
allColumns <- .local$allColumns
sw.va <- .local$sw.view.va
info <- widget$getPathAtPos(event$x, event$y)
selectedColumns <- GetSelectedColumns(.local)
# This block deals with dragging outside the view rectangle
if(is.null(info$column) || is.null(info$path)){
view <- .local$view
ptr <- view$getBinWindow()$getPointer()
vr <- view$getVisibleRect()$visible.rect
if(is.null(info$column))
info$column <- widget$getPathAtPos(event$x, 0)$column
if(is.null(info$column)){
sw.ha <- .local$sw.ha
sw.ha.value <- sw.ha$value
direction <- ifelse (ptr$x - sw.ha.value <= vr$width/2, -1, 1)
if(direction == 1) col.idx <- length(allColumns)
if(direction == -1) col.idx <- 1
} else {
col.idx <- GetColIdx(info$column)
}
if(is.null(info$path)){
if(ptr$y < 0) ptr$y <- 0
info$path <- widget$getPathAtPos(0, ptr$y)$path
}
if(is.null(info$path)) { #
sw.va <- .local$sw.view.va
sw.va.value <- sw.va$value
direction <- ifelse (sw.va$value+ptr$y <= vr$height/2, -1, 1)
if(direction == 1) row.idx <- nrow(.local$theFrame)
if(direction == -1) row.idx <- 1
} else {
row.idx <- info$path$getIndices()+1
}
} else {
row.idx <- info$path$getIndices()+1
col.idx <- GetColIdx(info$column)
}
# restrict selection from last column
# New code
if (event[["button"]] == as.integer(1) && !is.null(.local$selections)){
if(typ == GdkEventType['button-release']){ # single clicked
if(as.flag(stat) & GdkModifierType['control-mask']){
stopifnot(!is.null(.local$selections)
&& length(.local$selections))
.local$selections[[length(.local$selections)]]$end <-
c(row.idx=row.idx, col.idx=col.idx)
} else if(as.flag(stat) & GdkModifierType['shift-mask']){
} else {
stopifnot(!is.null(.local$selections))
.local$selections[[1]]$end <- c(row.idx=row.idx, col.idx=col.idx)
}
}
}
if(as.flag(stat) & GdkModifierType['control-mask']){
#selectedColumns <- union(selectedColumns, col.idx)
} else if (!is.null(.local$start.column.select) && length(col.idx)) {
selectedColumns <- .local$start.column.select:col.idx
} else if (length(col.idx)) {
selectedColumns <- col.idx
}# otherwise col.idx is NA
# This may or may not be needed. 4-10-10
.local$rectangles <- list() # reset our rectangle-drawing
# 3-2-10
UpdateSelectionRectangle(.local)
# split into rectangles
# if we're still using the timeout to paint rectangles,
# try to remove it
if(!is.null(.local$do.paint.timeout)) {
gSourceRemove(.local$do.paint.timeout)
.local$do.paint.timeout <- NULL
}
}
return(FALSE)
}
# Sync column selection with mouse-down
ViewMotionNotify <- function(widget, event){
# while(gtkEventsPending())
# gtkMainIteration()
# allColumns <- .local$allColumns
#.local$last.coord <-
if(0){ # code for making pointer go to cross on drag handle
pointer <- gdkWindowGetPointer(event[["window"]])
pp <- gtkTreeViewGetPathAtPos(widget, event[["x"]], event[["y"]])
dis <- gtkWidgetGetDisplay(widget)
win <- gtkWidgetGetWindow(widget)
if ((pp$cell.x-2)^2 + (pp$cell.y-2)^2 < 50){
gdkWindowSetCursor(win, gdkCursorNewFromName(dis, "cross"))
} else {
gdkWindowSetCursor(win, gdkCursorNewFromName(dis, "arrow"))
}
}
# if (as.flag(pointer$mask) & GdkModifierType["button1-mask"]){
# info <- gtkTreeViewGetPathAtPos(widget, pointer[["x"]], pointer[["y"]])
# if (info$retval){
# col.idx <- GetColIdx(info$column)
# if(is.null(.local$start.select.column)) .local$start.select.column <- col.idx
# new.sel <- sort(.local$start.select.column:col.idx)
# UpdateColumnSelection(.local, new.sel)
# }
# }
return(FALSE)
}
Cell3rdButtonMenu <- function(.local, row.idx, col.idx){
m <- gtkMenu()
pasteItem <- gtkMenuItem("Paste (Ctrl-V)")
m$append(pasteItem)
gSignalConnect(pasteItem, "activate", function(...)
ReadFromClipboardWrapper(.local))
if(!is.null(col.idx) && !is.null(row.idx)){
cutItem <- gtkMenuItem("Cut")
copyItem <- gtkMenuItem("Copy (Ctrl-C)")
copyWithNamesItem <- gtkMenuItem("Copy With Names (Ctrl-Shift-C)")
#m$append(cutItem)
m$append(copyItem)
m$append(copyWithNamesItem)
#lapply(c(cutItem), gtkWidgetSetSensitive, FALSE)
gSignalConnect(copyItem, "activate",
function(...) {
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
nf <- GetSelectedCells(.local)
if(!is.null(nf))
CopyToClipboard(nf,
do.rownames=FALSE, do.colnames=FALSE)
})
gSignalConnect(copyWithNamesItem, "activate",
function(...) {
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
nf <- GetSelectedCells(.local)
if(!is.null(nf))
CopyToClipboard(nf,
do.rownames=!RowNamesAreNull(.local$theFrame), do.colnames=!ColNamesAreNull(.local$theFrame))
})
m$append(gtkSeparatorMenuItem())
view <- .local$view
theFrame <- .local$theFrame
editFactorsItem <- gtkMenuItem("Edit Factors...")
randomizeItem <- gtkMenuItem("Randomize Selected")
fillItem <- gtkMenuItem("Fill Down")
fillCyclicItem <- gtkMenuItem("Fill In Cycles")
m$append(editFactorsItem)
m$append(randomizeItem)
m$append(fillItem)
m$append(fillCyclicItem)
typ <- GetClasses(.local$theFrame)[col.idx+COLUMN_OFFSET]
if(typ != "factor")
lapply(c(editFactorsItem, fillCyclicItem), gtkWidgetSetSensitive, FALSE)
gSignalConnect(editFactorsItem, "activate", function(...) {
# DoFactorEditor(theFrame, .local, col.idx + COLUMN_OFFSET))
DoFactorEditor(theFrame, .local$toplevel, col.idx + COLUMN_OFFSET,
FactorEditorHandler, data=.local)
})
gSignalConnect(randomizeItem, "activate", function(...){
sr <- GetSelectedRows(.local$view, .local)
if(length(sr)==0) sr <- 1:(dim(.local$theFrame)[1]-EXTRA_ROW)
if(length(sr)){
entry.frame <- theFrame[sr, GetSelectedColumns(.local)+COLUMN_OFFSET, drop=F]
entry.frame <- entry.frame[sample(1:(dim(entry.frame)[1])),,drop=F]
task <- list(list(func="ChangeCells",
arg = list(nf=entry.frame, row.idx=sr, col.idx=GetSelectedColumns(.local)+COLUMN_OFFSET)))
DoTaskWrapper(.local, task)
}
})
gSignalConnect(fillItem, "activate", function(...){
sr <- GetSelectedRows(.local$view, .local)
sc <- GetSelectedColumns(.local)+COLUMN_OFFSET
if(length(sr)==0) sr <- 1:(dim(.local$theFrame)[1]-EXTRA_ROW) # None selected: fill the whole column
if(length(sr)==1) sr <- sr:(dim(.local$theFrame)[1]-EXTRA_ROW) # One selected: fill everything below
if(length(sr) && length(sc)){
# if the user's selected noncontiguous rows, we're still drawing just one selected rectangle
# so we want to fill the whole thing anyway
sr <- range(sr)[1]:range(sr)[2]
entry.frame <- theFrame[sr, sc, drop=F]
for(jj in 1:length(sc))
entry.frame[,jj] <- entry.frame[1,jj]
task <- list(list(func="ChangeCells",
arg = list(nf=entry.frame, row.idx=sr, col.idx=GetSelectedColumns(.local)+1)))
DoTaskWrapper(.local, task)
}
})
gSignalConnect(fillCyclicItem, "activate", function(...) {
sr <- GetSelectedRows(.local$view, .local)
cc <- col.idx+COLUMN_OFFSET
df1 <- dim(.local$theFrame)[1]
if(length(sr)==0) sr <- 1:(dim(.local$theFrame)[1]-EXTRA_ROW) # None selected: fill the whole column
if(length(sr)==1) sr <- sr:(dim(.local$theFrame)[1]-EXTRA_ROW) # One selected: fill everything down
if(length(sr) < 2 && df1 > 1) sr <- 1:(df1-EXTRA_ROW)
if(length(sr)) {
# if the user's selected noncontiguous rows, we're still drawing just one selected rectangle
# so we want to fill the whole thing anyway
sr <- range(sr)[1]:range(sr)[2]
DoBlockSize(
theFrame[sr, cc],
.local$toplevel,
BlockSizeHandler,
data = list(.local=.local, row.idx=sr, col.idx=cc),
start.lvl=theFrame[sr[1], cc])
}
})
} #row.idx, col.idx != null
# if not editable, remove most options
if(!.local$editable) lapply(c(pasteItem, cutItem, editFactorsItem, randomizeItem, fillItem, fillCyclicItem), gtkWidgetSetSensitive, FALSE)
lapply(c(randomizeItem, fillItem, fillCyclicItem), gtkWidgetSetSensitive, FALSE)
return(m)
}
# Function to call when the data is sorted
SortHandler <- function(new.order, .local){
dd <- dim(.local$theFrame)
if(dd[1] > 1){
theFrame <- .local$theFrame[new.order, ,drop=F]
task <- list(
list(func="ChangeRowNames",
arg = list(theNames = rownames(theFrame), row.idx=1:(dd[1]-EXTRA_ROW))),
list(func="ChangeCells",
arg = list(nf=theFrame, row.idx=1:(dd[1]-EXTRA_ROW))))
DoTaskWrapper(.local, task)
}
}
OpenFileWrapper <- function(fileName, .local){
stopifnot(!is.null(fileName) && !is.na(fileName) && nchar(fileName))
ext <- strsplit(fileName, "[.]")[[1]]
ext <- tolower(ext[length(ext)])
if(identical(ext, "csv")){
sep <- ifelse(identical(options("OutDec")[[1]], "."), ",", ";")
} else if (identical(ext, "txt")) {
sep <- "\t"
} else {
sep <- ""
}
rfc <- ReadFromClipboard(fromFile=TRUE, fileName=fileName,
sep=sep)
dat <- rfc$xx
if(!is.null(dat) && length(dim(dat))==2){
dat <- MakeInternalDataFrame(rfc$xx)
ReplaceEditWindow(dat, .local, kill.history=TRUE)
dataset.name <- basename(fileName)
if(nchar(ext))
dataset.name <- substr(dataset.name, 1, nchar(dataset.name)-nchar(ext)-1)
.local$dataset.name <- dataset.name
}
}
OpenFile <- function(menuitem, user.data){
.local <- user.data
fileName <- my.gfile(type="open", multi=F)
if(!is.null(fileName) && !is.na(fileName) && nchar(fileName) && file.exists(fileName)){
OpenFileWrapper(fileName, .local)
DoUserCall("OnLoad", list(sourc=fileName, typ="file"), .local)
}
}
OpenURL <- function(menuitem, user.data){
.local <- user.data
rv <- run.dialog(dlg.list=list(title="Enter URL", url.stringItem="", label = "Enter URL here"))
if(!is.null(rv) && nchar(rv$args$url)){
OpenFileWrapper(rv$args$url, .local)
DoUserCall("OnLoad", list(sourc=rv$args$url, typ="url"), .local)
}
}
Corner3rdButtonMenu <- function(.local){
theFrame <- .local$theFrame
m <- gtkMenu()
cutItem <- gtkMenuItem("Cut")
copyItem <- gtkMenuItem("Copy")
pasteItem <- gtkMenuItem("Paste...")
m$append(cutItem)
m$append(copyItem)
m$append(pasteItem)
m$append(gtkSeparatorMenuItem())
gSignalConnect(copyItem, "activate", function(...) {
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
CopyToClipboard(MakeExternalDataFrame(theFrame, .local), do.rownames=!RowNamesAreNull(theFrame), do.colnames=!ColNamesAreNull(theFrame))
})
gSignalConnect(pasteItem, "activate", function(...){
ReadFromClipboardWrapper(.local)
})
lapply(c(cutItem),
gtkWidgetSetSensitive, FALSE)
renameItem <- gtkMenuItem("Rename Dataset")
gSignalConnect(renameItem, "activate", function(...) {
obj <- .local$rowname.column.obj$eventbox
EditHeaderBox(obj, handler = function(obj, event, data){
.local = data$.local
box = data$box
label = data$label
getText <- obj$getText()
if(nchar(getText) > 0) {
#label$setText(getText)
#assign(getText, MakeExternalDataFrame(.local$theFrame, .local$dataset.class), envir=.GlobalEnv)
oldname <- .local$dataset.name
.local$dataset.name <- getText
# 3-13-10
to.external <- MakeExternalDataFrame(.local$theFrame, .local)
nam <- .local$dataset.name
# clean up spaces
my.assign <- function(nam){
eval(parse(text=paste(paste(".GlobalEnv", nam, sep="$"), "<- to.external")))
DoUserCall("OnRename", list(name=nam, oldname=oldname), .local)
DoUserCall("OnLoad", list(sourc=nam, typ="rdata"), .local)
}
tryCatch({
my.assign(nam)
}, error = function(e) { # User called it something strange
my.assign(deparse(nam))
})
message(paste("RGtk2DfEdit: Creating dataset", .local$dataset.name, "in global environment."))
}
obj$destroy()
box$packEnd(label, FALSE, FALSE, 0)
gtkWidgetSetState(.local$rowname.column.obj$button, as.integer(0))
FALSE
}, data=list(.local=.local, data=NULL))
}
) #..
m$append(renameItem)
m$append(gtkSeparatorMenuItem())
openItem <- gtkMenuItem("Open File (Ctrl-O)")
m$append(openItem)
gSignalConnect(openItem, "activate", OpenFile, data=.local)
openURLItem <- gtkMenuItem("Open URL (Ctrl-U)")
m$append(openURLItem)
gSignalConnect(openURLItem, "activate", OpenURL, data=.local)
saveItem <- gtkMenuItem("Save As CSV... (Ctrl-S)")
m$append(saveItem)
gSignalConnect(saveItem, "activate", function(...){
tryCatch({
theName <- paste(.local$dataset.name, "csv", sep=".")
theFile <- my.gfile(initialfilename=theName, type="save", multi=F)
if(length(theFile) > 0 && nchar(theFile) && !is.na(theFile)){
write.csv(MakeExternalDataFrame(.local$theFrame, .local), theFile, quote=F, row.names=!RowNamesAreNull(.local$theFrame))
}
}, error = function(e){warning(e)})
})
m$append(gtkSeparatorMenuItem())
sortItem <- gtkMenuItem("Sort...")
gSignalConnect(sortItem, "activate", function(...){
dd <- dim(.local$theFrame)
if (EXTRA_ROW) DoSortDialog(.local$theFrame[-dd[1], -dd[2],drop=F], SortHandler, .local)
else
DoSortDialog(.local$theFrame[,-dd[2],drop=F], SortHandler, .local)
})
m$append(sortItem)
m$append(gtkSeparatorMenuItem())
# move dataset 1 column along
ordinalItem <- gtkMenuItem("Default Rows")
gSignalConnect(ordinalItem, "activate", function(...) {
dd1 <- dim(.local$theFrame)[1]
if(dd1 > 1){
task <- list(
list(func="ChangeRowNames",
arg = list(theNames = 1:(dd1-EXTRA_ROW), row.idx=1:(dd1-EXTRA_ROW))))
DoTaskWrapper(.local, task)
}
})
m$append(ordinalItem)
m$append(gtkSeparatorMenuItem())
ordinal2Item <- gtkMenuItem("Default Columns")
gSignalConnect(ordinal2Item, "activate", function(...) {
dd2 <- dim(.local$theFrame)[2]
if(dd2 > 2){
task <- list(
list(func="ChangeColumnNames",
arg = list(theNames = DEFAULT_COLNAMES[1:(dd2-2)], col.idx=2:(dd2-1))))
DoTaskWrapper(.local, task)
}
})
m$append(ordinal2Item)
m$append(gtkSeparatorMenuItem())
#lapply(renameItem, gtkWidgetSetSensitive, FALSE)
m$append(gtkSeparatorMenuItem())
aboutItem <- gtkMenuItem("About...")
m$append(aboutItem)
gSignalConnect(aboutItem, "activate", function(...){
dlg <- gtkAboutDialogNew(show=F)
dlg["authors"] <- c("Tom Taverner <t.taverner@gmail.com>",
"for the Department of Energy (PNNL, Richland, WA)",
"2010, Battelle Memorial Institute",
"Contributions from John Verzani and Liviu Andronic",
"RGtk2 by Michael Lawrence and Duncan Temple Lang",
"cairoDevice by Michael Lawrence")
dlg["program-name"] <- "RGtk2DfEdit"
dlg["comments"] <- "A spreadsheet data frame editor for the R environment"
dlg["copyright"] <- "GPLv2"
dlg["version"] <- VERSION_STRING
gtkDialogRun(dlg)
gtkWidgetDestroy(dlg)
})
if(!.local$editable) lapply(c(pasteItem, cutItem, renameItem, sortItem, ordinalItem, ordinal2Item), gtkWidgetSetSensitive, FALSE)
return(m)
}
Row3rdButtonMenu <- function(.local, row.idx){
theFrame <- .local$theFrame
m <- gtkMenu()
cutItem <- gtkMenuItem("Cut")
copyItem <- gtkMenuItem("Copy (Ctrl-C)")
pasteItem <- gtkMenuItem("Paste (Ctrl-V)")
m$append(cutItem)
m$append(copyItem)
m$append(pasteItem)
m$append(gtkSeparatorMenuItem())
gSignalConnect(copyItem, "activate", function(...) {
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
gsr <- GetSelectedRows(.local$view.rn, .local)
CopyToClipboard(
theFrame[gsr, -c(1, dim(theFrame)[2]), drop=F],
do.rownames=!RowNamesAreNull(theFrame), do.colnames=F)
})
gSignalConnect(pasteItem, "activate", function(...) {
dat <- ReadFromClipboard(.local, row.names=1, sep="\t", stringsAsFactors=F)$xx # character vector
if(!is.null(dat)){
if(!length(row.idx)) row.idx <- 1
col.idx <- 1
row.idx <- row.idx[1]
task <- GetTaskPasteIn(.local$theFrame, dat, row.idx, col.idx+COLUMN_OFFSET, do.colnames=F, do.rownames = F)
DoTaskWrapper(.local, task)
}
})
renameItem <- gtkMenuItem("Rename Row")
m$append(renameItem)
m$append(gtkSeparatorMenuItem())
gSignalConnect(renameItem, "activate", function(...) {
info <- gtkTreeViewGetCursor(.local$view.rn)
gtkTreeViewSetCursorOnCell(.local$view.rn, info$path, info$focus.column, info$focus.column$getCellRenderers()[[1]], TRUE)
})
insertItem <- gtkMenuItem("Insert")
gSignalConnect(insertItem, "activate", function(...) {
task <- list(list(func="InsertNARows",
arg = list(row.idx=row.idx)))
DoTaskWrapper(.local, task)
})
deleteItem <- gtkMenuItem("Delete")
gSignalConnect(deleteItem, "activate", function(...){
sr <- GetSelectedRows(.local$view.rn, .local)
if(length(sr)){
# We have to block the selection-changed signal, otherwise we get a crash
# This is because this fires the signal off for each deleted row!
task <- list(list(func="DeleteRows", arg = list(row.idx=sr)))
#gSignalHandlerBlock(.local$ss.rn, .local$ss.rn.changed.signal)
.local$rectangles <- list()
DoTaskWrapper(.local, task)
#gtkTreeSelectionUnselectAll(.local$ss.rn)
#gSignalHandlerUnblock(.local$ss.rn, .local$ss.rn.changed.signal)
}
})
clearItem <- gtkMenuItem("Clear Contents")
m$append(insertItem)
m$append(deleteItem)
m$append(clearItem)
gSignalConnect(clearItem, "activate", function(...){
row.idx <- GetSelectedRows(.local$view.rn, .local)
RowNamesClearContents(row.idx, .local)
})
lapply(c(cutItem), gtkWidgetSetSensitive, FALSE)
if(!length(GetSelectedRows(.local$view.rn, .local)))
lapply(c(deleteItem, pasteItem), gtkWidgetSetSensitive, FALSE)
if(EXTRA_ROW && row.idx == dim(theFrame)[1])
lapply(c(cutItem, copyItem, deleteItem, pasteItem, clearItem, cutItem),
gtkWidgetSetSensitive, FALSE)
m$append(gtkSeparatorMenuItem())
setAsNamesItem <- gtkMenuItem("To Column Names")
m$append(setAsNamesItem)
gSignalConnect(setAsNamesItem, "activate", function(...){
theNames <- as.character(theFrame[row.idx,-1])
theNames[is.na(theNames)] <- ""
theNames <- make.unique(theNames)
dd2 <- dim(.local$theFrame)[2]
task <- list(
list(func="ChangeColumnNames",
arg = list(theNames = theNames, col.idx=2:dd2)),
list(func="DeleteRows",
arg = list(row.idx=row.idx))
)
DoTaskWrapper(.local, task)
})
if(!.local$editable) lapply(c(pasteItem, cutItem, deleteItem, insertItem, clearItem, setAsNamesItem), gtkWidgetSetSensitive, FALSE)
return(m)
}
ColumnClearContents <- function(col.idx, .local){
if(length(col.idx)==0) return(FALSE)
nf <- .local$model[,col.idx, drop=F]
stopifnot(ncol(nf) > 0)
nf[,] <- ""
task <- list(list(func="ChangeCells",
arg = list(nf=nf, col.idx=col.idx)))
DoTaskWrapper(.local, task)
}
Column3rdButtonMenu <- function(.local, col.idx){
theFrame <- .local$theFrame
typ <- GetClasses(theFrame)[col.idx+COLUMN_OFFSET]
lastColumn <- col.idx == length(.local$allColumns) # is this the last column
theColumn <- theFrame[,col.idx+COLUMN_OFFSET,drop=F]
m <- gtkMenu()
cutItem <- gtkMenuItem("Cut")
copyItem <- gtkMenuItem("Copy")
pasteItem <- gtkMenuItem("Paste")
m$append(cutItem)
m$append(copyItem)
m$append(pasteItem)
m$append(gtkSeparatorMenuItem())
gSignalConnect(copyItem, "activate",
function(...) {
w <- TransientWindow("Copying...", .local)
on.exit(w$destroy())
CopyToClipboard(.local$theFrame[-dim(.local$theFrame)[1],GetSelectedColumns(.local)+1,drop=F],
do.colnames=T)
})
gSignalConnect(pasteItem, "activate", function(...) {
#dat <- ReadFromClipboard(header=T, sep="\t", stringsAsFactors=F)$xx # character vector
#task <- GetTaskPasteIn(.local$theFrame, dat,
# 1, col.idx+COLUMN_OFFSET, do.colnames= T)
#DoTaskWrapper(.local, task)
ReadFromClipboardWrapper(.local)
})
renameItem <- gtkMenuItem("Rename Column")
gSignalConnect(renameItem, "activate", function(...) {
obj <- .local$allColumns[[col.idx]]$eventbox
EditHeaderBox(obj, handler = function(obj, event, data){
col.idx = data$data
box = data$box
label = data$label
.local <- data$.local
new.name <- obj$getText()
task <- list(list(func="ChangeColumnNames",
arg = list(theNames = new.name, col.idx=col.idx+COLUMN_OFFSET)))
obj$destroy()
box$packEnd(label, FALSE, FALSE, 0)
if (nchar(new.name) > 0 && new.name != colnames(.local$theFrame)[col.idx+COLUMN_OFFSET]){
DoTaskWrapper(.local, task)
#box$setTooltipText(new.name)
box$setTooltipText(paste(DEFAULT_COLNAMES[col.idx], "\n", new.name, sep=""))
}
FALSE
}, data=list(.local=.local, col.idx=col.idx))
}
) #..
insertItem <- gtkMenuItem("Insert")
deleteItem <- gtkMenuItem("Delete")
clearItem <- gtkMenuItem("Clear")
m$append(renameItem)
m$append(gtkSeparatorMenuItem())
m$append(insertItem)
m$append(deleteItem)
m$append(clearItem)
m$append(gtkSeparatorMenuItem())
gSignalConnect(clearItem, "activate", function(...) ColumnClearContents(GetSelectedColumns(.local)+COLUMN_OFFSET, .local))
gSignalConnect(insertItem, "activate", function(...) {
task <- list(list(func="InsertNAColumns",
arg = list(col.idx=col.idx+COLUMN_OFFSET)))
DoTaskWrapper(.local, task)
})
gSignalConnect(deleteItem, "activate", function(...) {
sc <- GetSelectedColumns(.local)+COLUMN_OFFSET
if(length(sc)){
task <- list(list(func="DeleteColumns",
arg = list(col.idx=GetSelectedColumns(.local)+COLUMN_OFFSET)))
DoTaskWrapper(.local, task)
}
})
sortItem <- gtkMenuItem("Sort...")
gSignalConnect(sortItem, "activate", function(...) {
dd <- dim(.local$theFrame)
DoSortDialog(.local$theFrame[, -dd[2],drop=F], SortHandler, .local)
})
m$append(sortItem)
m$append(gtkSeparatorMenuItem())
lapply(c(cutItem), gtkWidgetSetSensitive, FALSE)
if(col.idx == length(.local$allColumns))
lapply(c(deleteItem), gtkWidgetSetSensitive, FALSE)
# If it's a factor, allow coercion to level type or numeric ordering
if ("factor"%in%typ[[1]]) {
item <- gtkCheckMenuItem("To Factor Levels")
levelType <- class(levels(theFrame[,col.idx+COLUMN_OFFSET]))
if(!is.atomic(levelType)) levelType <- "character" # default to character
gSignalConnect(item, "button-release-event", function(item, evt, levelType){
task <- list(list(func="CoerceColumns",
arg = list(theClasses = levelType, col.idx=col.idx+COLUMN_OFFSET, to.levels=TRUE)))
DoTaskWrapper(.local, task)
m$popdown()
return(TRUE)
}, levelType)
m$append(item)
item <- gtkCheckMenuItem("To Factor Ordering")
gSignalConnect(item, "button-release-event", function(item, evt, levelType){
task <- list(list(func="CoerceColumns",
arg = list(theClasses = "integer", col.idx=col.idx+COLUMN_OFFSET)))
DoTaskWrapper(.local, task)
m$popdown()
return(TRUE)
}, levelType)
m$append(item)
} else {
dataTypeNames <- list(Character="character", Integer="integer", Factor="factor", Logical="logical", Numeric="numeric")
dataTypeItems <- list()
for(theNewTypeName in names(dataTypeNames)){
item <- gtkCheckMenuItem(theNewTypeName)
item$setDrawAsRadio(TRUE)
dataTypeItems[[length(dataTypeItems)+1]] <- item
gSignalConnect(item, "button-release-event", function(item, evt, theNewTypeName){
task <- list(list(func="CoerceColumns",
arg = list(theClasses = dataTypeNames[[theNewTypeName]], col.idx=GetSelectedColumns(.local)+COLUMN_OFFSET)))
DoTaskWrapper(.local, task)
m$popdown()
return(TRUE)
}, theNewTypeName)
if (dataTypeNames[[theNewTypeName]]%in%typ[[1]]) item$setActive(TRUE)
m$append(item)
}
}
m$append(gtkSeparatorMenuItem())
editFactorsItem <- gtkMenuItem("Edit Factors...")
m$append(editFactorsItem)
m$append(gtkSeparatorMenuItem())
abbreviateItem <- gtkMenuItem("Shorten")
m$append(abbreviateItem)
setAsNamesItem <- gtkMenuItem("To Row Names")
m$append(setAsNamesItem)
gSignalConnect(editFactorsItem, "activate", function(...)
DoFactorEditor(theFrame, .local$toplevel, col.idx + COLUMN_OFFSET,
FactorEditorHandler, data=.local))
if(typ != "factor") editFactorsItem$setSensitive(FALSE)
gSignalConnect(abbreviateItem, "activate", function(...){
abcol <- data.frame(X=cbind(abbreviate(as.character(theColumn[[1]]), minlength=10)))
task <- list(
list(func="CoerceColumns",
arg = list(theClasses = "character", idx=col.idx+COLUMN_OFFSET)),
list(func="ChangeCells",
arg = list(nf=abcol, col.idx=col.idx+COLUMN_OFFSET))
)
DoTaskWrapper(.local, task)
})
gSignalConnect(setAsNamesItem, "activate", function(...){
theNames <- as.character(theColumn[[1]])
theNames[is.na(theNames)] <- ""
theNames <- make.unique(theNames)
dd1 <- dim(.local$theFrame)[1]
task <- list(
list(func="ChangeRowNames",
arg = list(theNames = theNames, row.idx=1:dd1)),
list(func="DeleteColumns",
arg = list(col.idx=col.idx+COLUMN_OFFSET))
)
DoTaskWrapper(.local, task)
})
if(lastColumn) { # disable everything except for insert
lapply(m$getChildren(), gtkWidgetSetSensitive, FALSE)
lapply(list(insertItem), gtkWidgetSetSensitive, TRUE)
}
if(!.local$editable) { # disable everything except for insert
lapply(m$getChildren(), gtkWidgetSetSensitive, FALSE)
lapply(list(copyItem), gtkWidgetSetSensitive, TRUE)
}
return(m)
}
# just update the dataset name when we change the text
CornerBoxButtonPress <- function (obj, event, data){
.local <- data$.local
#gSignalHandlerBlock(.local$ss.rn, .local$ss.rn.changed.signal)
#gtkWidgetGrabFocus(.local$view)
#gSignalHandlerUnblock(.local$ss.rn, .local$ss.rn.changed.signal)
# get out of editing or selection
button <- event[['button']]
typ <- event[['type']]
stat <- event[['state']]
col.idx <- data$col.idx
if (button == as.integer(1)){
if(typ == as.integer(4)){ # single clicked
#gtkTreeViewGetSelection(.local$view.rn)$unselectAll()
SelectAll(.local)
} else if(0 && typ == as.integer(5)){ # turned off double clicked
gtkWidgetSetState(.local$rowname.column.obj$button, as.integer(1))
EditHeaderBox(obj, handler = function(obj, event, data){
.local = data$.local
box = data$box
label = data$labeld
getText <- obj$getText()
if(nchar(getText) > 0) {
label$setText(getText)
#assign(getText, MakeExternalDataFrame(.local$theFrame, .local$dataset.class), envir=.GlobalEnv)
.local$dataset.name <- getText
# 3-13-10
to.external <- MakeExternalDataFrame(.local$theFrame, .local)
nam <- .local$dataset.name
# clean up spaces
my.assign <- function(nam){
eval(parse(text=paste(paste(".GlobalEnv", nam, sep="$"), "<- to.external")))
}
tryCatch({
my.assign(nam)
}, error = function(e) { # User called it something strange
my.assign(deparse(nam))
})
message(paste("RGtk2DfEdit: Creating dataset", .local$dataset.name, "in global environment."))
}
obj$destroy()
box$packEnd(label, FALSE, FALSE, 0)
gtkWidgetSetState(.local$rowname.column.obj$button, as.integer(0))
FALSE
}, data=list(.local=.local, data=NULL))
} # end double clicked
} # end clicked
if (button == as.integer(3)){ # our popup menu
m <- Corner3rdButtonMenu(.local)
gtkMenuPopupHack(m, button = event$GetButton(),
activate.time = gdkEventGetTime(event))
return(FALSE)
}
return(TRUE)
}
ModelChangeDatasetName <- function(.local, theName){
.local$dataset.name <- theName
.local$rowname.column.obj$eventbox$getChildren()[[1]]$getChildren()[[1]]$setText(theName)
}
# Sets up column header box for editing
# handler is function to call on focus out event
# Passes to handler: list(data=data, box=box, label=label, .local=.local)
EditHeaderBox <- function(obj, handler, data){
.local <- data$.local
col.idx <- data$col.idx
box = obj$getChildren()[[1]]
label = box$getChildren()[[1]]
height = box$allocation$height-HEADER_BOX_MARGIN
width = box$allocation$width
box$remove(label)
entry <- gtkEntryNew()
entry$setText(label$getText())
entry$setHasFrame(FALSE)
#entry$modifyBase(as.integer(1), selectedColumnColor)
entry$modifyBase(as.integer(1), as.GdkColor(c(255,255,255)*256))
makeBlack <- function(y) entry$modifyText(y, as.GdkColor("black"))
sapply(as.integer(0:1), makeBlack)
entry$modifyBase(as.integer(1), as.GdkColor(c(255,255,255)*256))
#entry$setAlignment(1)
if(is.numeric(height) && length(height)==1 && is.numeric(width) && length(width)==1){
entry$setSizeRequest(width, height) # 1 pixel margins I guess?
}
box$packEnd(child=entry, expand=TRUE, fill=TRUE, padding=0)
entry$grabFocus()
gSignalConnect(entry, "key-press-event", function(obj, event, data=col.idx){
#if (event[["keyval"]]%in%myValidNavigationKeys) .local$view$grabFocus()
if (event[["keyval"]]%in%c(GDK_Return, GDK_Escape)) .local$view$grabFocus()
return(FALSE)
})
#gSignalConnect(entry, "button-press-event", function(obj, event, data=col.idx){
# return(TRUE)
#})
gSignalConnect(entry, "focus-out-event", handler, data=list(data=col.idx, box=box, label=label, .local=.local))
#gSignalConnect(entry, "unrealize", handle)
#.local$entry <- entry
return(TRUE)
}
# column header clicked
# obj is eventbox
ColumnHeaderButtonPress <- function (obj, event, data){
.local <- data$.local
.local$draw.cursor <-FALSE
view <- .local$view
if(!is.null(.local$entry)) {
if(!is.null(.local$entry.focus.out))
gSignalHandlerDisconnect(.local$entry, .local$entry.focus.out)
gtkWidgetUnrealize(.local$entry)
.local$entry <- NULL
.local$entry.focus.out <- NULL
gtkWidgetGrabFocus(view)
}
if(0 && !is.null(.local$entry)) {
#tryCatch({
if(gtkWidgetGetRealized(.local$entry))
gtkWidgetUnrealize(.local$entry)
.local$entry <- NULL
#}, silent=F)
}
if(0){
# get out of editing or selection
# To avoid condition where column header was just pressed and
# rows were previously selected, we block the changed signal
# kill any active entries
if(0 && !view$isFocus()) {
gSignalHandlerBlock(.local$ss.rn, .local$ss.rn.changed.signal)
view$grabFocus()
gtkTreeSelectionUnselectAll(.local$ss.rn)
gSignalHandlerUnblock(.local$ss.rn, .local$ss.rn.changed.signal)
}
} # if 0
view.rn <- .local$view.rn
model <- .local$model
allColumns <- .local$allColumns
# unselect the main view
if(0 && length(.local$rectangles)){
.local$rectangles <- list()
gdkWindowInvalidateRect(.local$viewGetBinWindow, NULL, FALSE)
}
if(length(.local$selections)){
}
#for(tv in c(view, view.rn)){
## pfc <- gtkTreeViewGetCursor(tv)
# gtkTreeSelectionUnselectAll(gtkTreeViewGetSelection(tv))
#}
#gtkTreeSelectionUnselectAll(gtkTreeViewGetSelection(view))
#gtkTreeSelectionUnselectAll(gtkTreeViewGetSelection(view.rn))
# prevents cursor showing up
#gtkWidgetGrabFocus(gtkWidgetGetParent(.local$view))
button <- event[['button']]
typ <- event[['type']]
stat <- event[['state']]
col.idx <- data$col.idx
#gtkTreeViewSetCursorOnCell(view, gtkTreePathNewFromString(0))
# ignore d-click on last column
# ignore if it's the end column
lastColumn <- col.idx == length(allColumns)
selectedColumns.new <- integer(0) # tom 092610
# update: don't let double-click edit.
if (0 && !lastColumn && button == as.integer(1) && typ == as.integer(5) && stat == as.integer(0)){ # double clicked
EditHeaderBox(obj, handler = function(obj, event, data){
col.idx = data$data
box = data$box
label = data$label
.local <- data$.local
new.name <- obj$getText()
task <- list(list(func="ChangeColumnNames",
arg = list(theNames = new.name, col.idx=col.idx+COLUMN_OFFSET)))
obj$destroy()
box$packEnd(label, FALSE, FALSE, 0)
if (new.name != colnames(.local$theFrame)[col.idx+COLUMN_OFFSET])
DoTaskWrapper(.local, task)
FALSE
}, data=list(.local=.local, col.idx=col.idx))
}
if (button == as.integer(1) && typ == as.integer(4)){ # column clicked
selections <- .local$selections
theFrameNRows <- nrow(.local$theFrame)
colSel <- list(start=c(row.idx=1, col.idx=col.idx),
end=c(row.idx=theFrameNRows, col.idx=col.idx))
selectedColumns <- GetSelectedColumns(.local)
if (as.flag(stat) & GdkModifierType['shift-mask']) { # range
if(is.null(selections) || !length(selections))
selections <- list(list(start=colSel$start))
# For the tricky case where we've selected a cell range
# and shift-click - unsure of best behavior
selections[[length(selections)]]$start['row.idx'] <- 1
selections[[length(selections)]]$end <- colSel$end
} else if (as.flag(stat) & GdkModifierType['control-mask']) { # range
# Simpler: Just add the column to whatever you clicked.
selections <- .local$selections
if(length(selections)){
# we've only selected the start - bad news
if( length(selections[[length(selections)]]) == 1)
return(FALSE);
#if(length(selections)[[length(selections)]] == 1)
# selections[[length(selections)]]$end <-
# selections[[length(selections)]]$start
xors <- get.xor.selection(selections)
aicx <- as.integer(colnames(xors))
selectedColumns <- aicx[colSums(xors)>0]
# if the column index is not in the range
# of selected columns,
# we want to add it
whichidx <- which(aicx==col.idx)
# if the entire column is already selected and TRUE
# then set it to FALSE
if(col.idx%in%selectedColumns){
#dont.add.flag <- TRUE
if(all(xors[,whichidx])
&& nrow(xors)==theFrameNRows){
xors[,whichidx] <- FALSE
selections <- get.xor.rectangles(xors)
} else {
xors[,whichidx] <- FALSE
selections <- get.xor.rectangles(xors)
selections[[length(selections)+1]] <- colSel
}
# otherwise set the whole column to TRUE
} else {
selections[[length(selections)+1]] <- colSel
}
# if length(selections)
} else {
selections[[length(selections)+1]] <- colSel
}
} else {
gtkTreeSelectionUnselectAll(.local$ss)
selections <- list()
selections[[length(selections)+1]] <- colSel
}
.local$selections <- selections
.local$do.paint <- TRUE
UpdateSelectionRectangle(.local)
gdkWindowInvalidateRect(.local$viewGetBinWindow, NULL, FALSE)
#return(FALSE)
# UpdateColumnSelection(.local, selectedColumns.new)
DoUserCall("ColumnClicked", list(idx = data$col.idx), .local)
} # clicked
if (button == as.integer(3)){ # our popup menu
selectedColumns <- GetSelectedColumns(.local)
if(!length(selectedColumns)){
selectedColumns.new <- col.idx
UpdateColumnSelection(.local, selectedColumns.new)
}
m <- Column3rdButtonMenu(.local, col.idx)
gtkMenuPopupHack(m, button = event$GetButton(),
activate.time = gdkEventGetTime(event))
return(FALSE)
}
return(TRUE)
}
GetVerticalPosition <- function(.local){
.local$sw.view.va$getValue()
}
SetVerticalPosition <- function(.local, value){
.local$sw.view.va$setValue(value)
.local$sw.rn.va$setValue(value)
}
# replace the entire gtktab
# new.df is the internal DF representation
# want to keep cursor position if it exists
ReplaceEditWindow <- function(theFrame, .local, kill.history=FALSE){
old.v.pos <- GetVerticalPosition(.local)
#old.h.pos <- .local$sw.ha$getValue()
UpdateColumnSelection(.local, integer(0))
if(kill.history) .local$undoStack <- NULL
.local$theFrame <- theFrame
gtktab.new <- MakeDFEditWindow(.local, theFrame)
.local$gtktab$destroy()
.local$gtktab <- gtktab.new
.local$group.main$packStart(.local$gtktab, TRUE, TRUE, 0)
.local$start.column.select <- NULL
gTimeoutAdd(50, function(){SetVerticalPosition(.local, old.v.pos); return(FALSE)})
}
NewColumnToggle <- function(model, j, width=64, is.row = F, is.editable=T, resizable=T){
renderer <- gtkCellRendererToggleNew()
column <- gtkTreeViewColumnNew()
#column$packStart(renderer)
gtkTreeViewColumnPackStart(renderer)
#column$setTitle(as.character(j-1))
gtkTreeViewColumnSetTitle(as.character(j-1))
gtkTreeViewColumnSetFixedWidth(column, width)
gtkTreeViewColumnSetSizing(column, GtkTreeViewColumnSizing['fixed'])
gtkTreeViewColumnSetResizable(column, resizable)
return(list(column=column, renderer=renderer, col.idx=j))
}
# Old code for pretty print:
# txt <- .Call("R_getGObjectProps", cell, "text", PACKAGE = "RGtk2") # ~5 microseconds
# if(txt == "1.#QNAN0" || txt == "-1.#IND00") # ~7 microseconds
# .Call("R_setGObjectProps", cell, list(text="NA"), PACKAGE = "RGtk2")
# else # ~13 microseconds, 22 if you suppress warnings
# .Call("R_setGObjectProps", cell, list(text=sprintf(SPRINTF_FORMAT, as.numeric(txt))), PACKAGE = "RGtk2")
# create a column with the title as the index
NewColumn <- function(model, j, width=64, is.row = F, is.editable=T, resizable=T, min.width=20, max.width=150){
renderer <- gtkCellRendererTextNew()
#gtkCellRendererTextSetFixedHeightFromFont(renderer, 1)
gObjectSetData(renderer, "column", j-1)
renderer['xalign'] <- 1
#if(is.editable){
renderer['editable-set'] <- TRUE
renderer['editable'] <- is.editable
#}
column <- gtkTreeViewColumnNewWithAttributes(as.character(j-1), renderer, text = j-1)
gtkTreeViewColumnSetFixedWidth(column, width)
gtkTreeViewColumnSetMinWidth(column, min.width) # for min width
gtkTreeViewColumnSetMaxWidth(column, max.width) # for min width
gtkTreeViewColumnSetSizing(column, GtkTreeViewColumnSizing['fixed'])
#gtkTreeViewColumnSetResizable(column, TRUE)
return(list(column=column, renderer=renderer, col.idx=j))
}
# Must be called after widget is mapped
MakeButtonAndEventBox <- function(col.obj, label.str, handler, .local){
label <- gtkLabelNew(label.str)
box <- gtkVBoxNew()
#gtkBoxPackEnd(box, label, FALSE, FALSE, 0)
eventbox <- gtkEventBoxNew()
gtkContainerAdd(eventbox, box)
view.col <- col.obj$column
gtkTreeViewColumnSetWidget(view.col, eventbox)
alignment <- gtkWidgetGetParent(eventbox)
gtkAlignmentSet(alignment, 0, 1, 1, 1)
col.idx <- GetColIdx(view.col)
#label2 <- gtkLabelNew(DEFAULT_COLNAMES[col.idx])
#gtkContainerAdd(box, label2)
box$packEnd(child=label, expand=TRUE, fill=TRUE, padding=0)
#gtkContainerAdd(box, label)
col.obj$eventbox <- eventbox
gtkWidgetModifyBg(eventbox, as.integer(1), selectedColumnColor)
col.obj$button <- gtkWidgetGetParent(gtkWidgetGetParent(alignment))
gSignalConnect(eventbox, "button-press-event", handler, data=list(col.idx=col.idx, .local=.local))
if(col.idx==0) {
txt <- "Right-click for options"
} else {
if (DEFAULT_COLNAMES[col.idx] == label.str)
txt <- DEFAULT_COLNAMES[col.idx]
else
txt <- paste(DEFAULT_COLNAMES[col.idx],
"\n", label.str, sep="")
}
gtkWidgetSetTooltipText(eventbox, txt)
return(col.obj)
}
# update.object: if you update the data frame at the same time
UpdateDfEditor <- function(.local, theFrame, rows.changed=NULL){
if (is.null(rows.changed)){
if(ncol(theFrame) != ncol(.local$model)){
ReplaceEditWindow(theFrame, .local)
} else {
cn <- colnames(theFrame)
for(jj in which(cn != colnames(.local$theFrame)))
ModelChangeColumnName(.local, jj, cn[jj])
.local$model$setFrame(theFrame)
}
.local$LAST_PATH <- MakeLastPath(theFrame)
} else {
.RGtkCall("R_r_gtk_data_frame_set", .local$model, theFrame, as.list(as.integer(rows.changed - 1)), F)
}
.local$theFrame <- theFrame
if(identical(.local$update.frame, TRUE)){
to.external <- MakeExternalDataFrame(.local$theFrame, .local)
#assign(.local$dataset.name, to.external, envir=.GlobalEnv)
#3-13-10
# Change this to work with embedded lists - different choices for data names
# if it's "iris", save it as iris
# if it's "xx$iris" and "xx" exists and is a list, save it as xx$iris
# if it's "abc$iris" and "abc" isn't a list, save it as "abc$iris"
#3-13-10
nam <- .local$dataset.name
# clean up spaces
my.assign <- function(nam){
eval(parse(text=paste(paste(".GlobalEnv", nam, sep="$"), "<- to.external")))
}
tryCatch({
my.assign(nam)
}, error = function(e) { # User called it something strange
my.assign(deparse(nam))
})
}
}
ModelChangeColumnName <- function(.local, idx, new.name)
.local$allColumns[[idx-1]]$eventbox$getChildren()[[1]]$getChildren()[[1]]$setText(new.name)
# function that takes a NewColumn returned object and adds
# eventboxes, buttons, etc
# MUST BE DONE AFTER VIEW IS MAPPED
InitColumn <- function(.local, col.obj, nam){
col.obj <- MakeButtonAndEventBox(col.obj, label.str=nam, handler=ColumnHeaderButtonPress, .local=.local)
if(0) gSignalConnect(col.obj$renderer, "editing-started", function(renderer, editable, path, data=list(.local=.local)){
#.local <- data$.local
#print("started")
FALSE
})
#if(1) gSignalConnect(col.obj$renderer, "editing-started", function(renderer, entry, path, data) {
# print("Here")
# gSignalConnect(entry, "map-event", function(entry, event, data=.local){
#print("A")
#Sys.sleep(1)
#print("B")
# insertion_point <- 0
# pixel_size <- entry$getLayout()$getPixelSize()
# click_info <- .local$last_click_info
# col_width <- click_info$column$getWidth()
# text_width <- pixel_size$width
# text_pos <- (click_info$cell.x - col_width + text_width)/(text_width)
# if(length(text_pos) != 1 || text_pos < 0 || text_pos > 1) text_pos <- 0
# insertion_point <- round(text_pos*nchar(entry$getText()))
# gtkEditableSelectRegion(entry, insertion_point, insertion_point)
# return(TRUE)
# })
# }, data=list(codl.idx=col.obj$col.idx, .local=.local))
if(1) gSignalConnect(col.obj$renderer, "editing-started", after=T, RendererEditingStarted, data=list(col.idx=col.obj$col.idx, .local=.local))
col.obj$state.set <- NULL
return(col.obj)
}
InitAllColumns <- function(.local, model, allColumns){
ds.colnames <- colnames(model)
for(j in 1:length(allColumns)){
allColumns[[j]] <- InitColumn(.local, allColumns[[j]], ds.colnames[j+1])
allColumns[[j]]$col.idx <- j+COLUMN_OFFSET
}
# The last column is not editable
allColumns[[j]]$renderer['editable'] <- FALSE
gtkTreeViewColumnSetResizable(allColumns[[j]]$column, FALSE)
#gtkWidgetModifyBg(allColumns[[j]]$eventbox, as.integer(1), bgColor)
return(allColumns)
}
MakeAVerticalScrollbar <- function(.local, gtktab, va){
vbar <- gtkVScrollbarNew(va)
gtktab$attach(vbar, 1, 2, 0, 1, 0, 5)
# scroll on this doesn't repaint, kill it
vbar$setEvents(GdkEventMask["button-motion-mask"])
gSignalConnect(vbar, "scroll-event", function(...) {
return(TRUE)
})
gSignalConnect(vbar, "button-press-event", function(...) {
if(!gtkWidgetIsFocus(.local$view) && !gtkWidgetIsFocus(.local$view.rn)) .local$view$grabFocus()
return(FALSE)
})
gSignalConnect(vbar, "button-release-event", function(...) {
.local$viewGetBinWindow$invalidateRect(NULL, FALSE)
return(FALSE)
})
return(vbar)
}
RowNamesExpose <- function(widget, event=NULL, data=NULL){
.local <- data
if(.local$do.paint && DO_CAIRO){
currentVadj <- gtkAdjustmentGetValue(.local$sw.view.va) # this
for(r in .local$rn.rectangles){
r$y <- r$y - currentVadj
selectedColorRgb <- c(49, 106, 197)
cr <- gdkCairoCreate(.local$view.rnGetBinWindow)
cairoSetSourceRgba(cr, selectedColorRgb[1]/256,
selectedColorRgb[2]/256, selectedColorRgb[3]/256, 0.2)
cairoRectangle(cr, r$x, r$y+1, r$width, r$height)
cairoClip(cr)
cairoPaint(cr)
}
}
return(FALSE)
}
ViewExpose <- function(widget, event=NULL, data=NULL){
.local <- data
isWindows <- PLATFORM_OS_TYPE == "windows"
if(.local$do.paint){
#cat("*")
currentVadj <- gtkAdjustmentGetValue(.local$sw.view.va) # this gets called *before* value-changed
for(r in .local$rectangles){
r$y <- r$y - currentVadj
gdkDrawRectangle(.local$viewGetBinWindow, .local$gc.invert, filled = F,
ifelse(isWindows, r$x, r$x-1),
ifelse(isWindows, r$y, r$y),
r$width+2,
ifelse(isWindows, r$height+2, r$height))
}
if(DO_CAIRO) {
for(r in .local$rectangles){
r$y <- r$y - currentVadj
selectedColorRgb <- c(49, 106, 197)
cr <- gdkCairoCreate(.local$viewGetBinWindow)
cairoSetSourceRgba(cr, selectedColorRgb[1]/256,
selectedColorRgb[2]/256, selectedColorRgb[3]/256, 0.2)
# cairoRectangle(cr, r$x, r$y+1, r$width, r$height)
cairoRectangle(cr,
r$x,
r$y+1,
ifelse(isWindows, r$width+2, r$width),
ifelse(isWindows, r$height+2, r$height))
cairoClip(cr)
cairoPaint(cr)
}
}
} # display mode
if(.local$draw.cursor){
# Draw the cursor
cursor.info <- gtkTreeViewGetCursor(widget)
path <- cursor.info$path
column <- cursor.info$focus.column
if(is.null(.local$entry) && !is.null(path) && !is.null(column)){
rect <- gtkTreeViewGetCellArea(widget, path, column)$rect
gdkDrawRectangle(.local$viewGetBinWindow, .local$gc.cursor, filled = F, rect$x, rect$y +1, rect$width,
ifelse(isWindows, rect$height-1, rect$height-2))
gdkDrawRectangle(.local$viewGetBinWindow, .local$gc.cursor, filled = T, rect$x+rect$width-4, rect$y+rect$height-1-3, 4, 3)
}
}
return(FALSE)
}
DoScrollUpdate <- function(obj, evt, data){
.local <- data
# if(!gtkWidgetIsFocus(obj)) gtkWidgetGrabFocus(obj)
while(gtkEventsPending())
gtkMainIteration()
dir <- evt[["direction"]]
va <- .local$vbar$getAdjustment()
if (dir == GdkScrollDirection["down"]) {
new.val <- min(va$getValue() + va[["step_increment"]], va[["upper"]] - va[["page_size"]])
va$setValue(new.val)
} else if (dir == GdkScrollDirection["up"]) {
va$setValue(va$getValue() - va[["step_increment"]])
}
FALSE
}
MakeDFEditWindow <- function(.local, theFrame, size.request=c(600, 300), col.width=64){
.local$allColumns <- NULL
.local$allow.key.flag <- TRUE
.local$col.width <- col.width
.local$do.paint <- TRUE
.local$doing.scroll <- FALSE
.local$doingEntryIntoFactor <- FALSE
.local$doingHScroll <- FALSE
.local$FIRST_PATH <- gtkTreePathNewFromString(0)
.local$flash.cursor <- TRUE
.local$last.time <- 0
.local$last.place.clicked <- NULL
.local$LAST_PATH <- NULL
.local$model <- NULL
.local$SCROLL_ROW_TIMEOUT <- 150
.local$scrollID <- 0
.local$draw.cursor <- TRUE
.local$theFrame <- theFrame
sw.view <- gtkScrolledWindowNew()
.local$sw.view <- sw.view
sw.view$setPolicy(GtkPolicyType['never'], GtkPolicyType['never'])
sw.view$setSizeRequest(size.request[1], size.request[2])
.local$LAST_PATH <- MakeLastPath(theFrame)
dataset.name <- .local$dataset.name
.local$model <- rGtkDataFrame()
view <- gtkTreeViewNewWithModel(.local$model)
.local$model$setFrame(.local$theFrame) # This is causing a memory leak...
gtktab <- gtkTableNew(2,2, FALSE)
.local$mapped <- FALSE
.local$view <- view
gtkTreeViewSetFixedHeightMode(view, TRUE)
allColumns <- vector("list", (dim(theFrame)[2]-1))
view.rn <- gtkTreeViewNewWithModel(.local$model)
.local$view.rn <- view.rn
view.rn$SetFixedHeightMode(TRUE)
rowname.column.obj <- NewColumn(.local$model, 1, width=10, is.row=T, resizable=F, is.editable=.local$editable)
.local$rowname.column.obj <- rowname.column.obj
gtkTreeViewAppendColumn(view.rn, rowname.column.obj$column)
col.sizing <- ifelse(identical(.local$autosize, TRUE), 'autosize', 'fixed')
for(j in 2:(dim(.local$model)[2])){
tmp <- NewColumn(.local$model, j, width=col.width, is.editable=T)#.local$editable)
gtkTreeViewAppendColumn(view, tmp$column)
gtkTreeViewColumnSetSizing(tmp$column, GtkTreeViewColumnSizing[col.sizing])
gtkTreeViewColumnSetResizable(tmp$column, TRUE)
allColumns[[j-1]] <- tmp
}
ss <- view$getSelection()
.local$ss <- ss
ss$setMode(as.integer(3)) # multiple
view$setRubberBanding(TRUE)
sw.view$add(view)
sw.view.va <- sw.view$getVadjustment()
.local$sw.view.va <- sw.view.va
sw.view.ha <- sw.view$getHadjustment()
.local$sw.ha <- sw.view.ha
.local$va <- sw.view.va
ss.rn <- view.rn$getSelection()
# 09-16-10 - this is screwing up memory caching
# 10-21-10 - this is causing a crash on row deletion
if(0) .local$ss.rn.changed.signal <- gSignalConnect(ss.rn, "changed", function(treeselection, data){
##print(.local$allow.key.flag)
# .local$allow.key.flag <- FALSE
#w <- TransientWindow("Updating Selection...", .local)
# # on.exit(w$destroy())
#PaintRowSelectionOnTimeout(.local)
PaintSelectionOnTimeout(.local, selection=treeselection, widget=.local$view.rn)
#.local$allow.key.flag <- TRUE
# if(.local$do.paint){
# .local$viewGetBinWindow$invalidateRect(NULL, FALSE)
# .local$do.paint <- FALSE
# }
}
)
.local$ss.rn <- ss.rn
ss.rn$setMode(as.integer(3)) # multiple
sw.rn <- gtkScrolledWindowNew()
sw.rn$add(view.rn)
view.rn$setSizeRequest(-1, 10)
sw.rn$setPolicy(GtkPolicyType['never'], GtkPolicyType['never'])
sw.rn.va <- sw.rn$getVadjustment()
.local$sw.rn.va <- sw.rn.va
#if(0){
for(ii in as.integer(0:4))
gtkWidgetModifyBase(view, ii, whiteColor)
#gtkWidgetModifyBase(view, GtkStateType['active'], whiteColor)
gtkWidgetModifyText(view, GtkStateType['selected'], as.GdkColor("black"))
gtkWidgetModifyText(view, GtkStateType['active'], as.GdkColor("black"))
sapply(list(view, view.rn), function(x){
sapply(as.integer(0:4),
function(y) gtkWidgetModifyFg(x, y, as.GdkColor("black")))
})
# It would be nice to change the background raised appearance
# for selected rows
#img_pixbuf = gdkPixbufNewFromFile(image_filename)
#img_pixbuf <- gdkPixbufNew("GDK_COLORSPACE_RGB", FALSE, 8, 1, 1)
#img_pixmap = img_pixbuf.render_pixmap_and_mask()[0]
#for state in (gtk.STATE_NORMAL, gtk.STATE_ACTIVE, #gtk.STATE_PRELIGHT,
# gtk.STATE_SELECTED, gtk.STATE_INSENSITIVE):
# style$bg_pixmap[state] = img_pixmap
#treeview.set_style(style)
for(ii in as.integer(0:4))
gtkWidgetModifyBase(view.rn, ii, bgColor)
gtkWidgetModifyText(view.rn, GtkStateType['selected'], as.GdkColor("black"))
gtkWidgetModifyText(view.rn, GtkStateType['active'], as.GdkColor("black"))
#}
view.rn$setEnableSearch(FALSE)
view$setEnableSearch(FALSE)
#view$setGridLines(as.integer(3))
#view.rn$setGridLines(as.integer(3))
.local$style <- view$getStyle
paned <- gtkHPanedNew()
paned$add1(sw.rn)
paned$add2(sw.view)
paned$setPosition(100)
gtktab$attach(paned, 0, 1, 0, 1, 5, 5)
hbar <- gtkHScrollbarNew(sw.view.ha)
gtktab$attach(hbar, 0, 1, 1, 2, 5, 0)
vbar <- MakeAVerticalScrollbar(.local, gtktab, sw.view.va)
.local$vbar <- vbar
## we're doing all this after we map
## Notebooks map too!
gSignalConnect(view, "map", after=T, data=.local, function(view, data){
.local <- data
if(.local$mapped) return(TRUE)
.local$mapped <- TRUE
.local$do.paint <- FALSE
#.local$rowname.column.obj <- MakeButtonAndEventBox(.local$rowname.column.obj,
# label.str=.local$dataset.name, handler=CornerBoxButtonPress, .local=.local)
# Change the displayed name to "" to avoid confusion.
.local$rowname.column.obj <- MakeButtonAndEventBox(.local$rowname.column.obj,
label.str="", handler=CornerBoxButtonPress, .local=.local)
allColumns <- InitAllColumns(.local, .local$model, allColumns)
.local$allColumns <- allColumns
gSignalConnect(view,"key-press-event", ViewKeyPress, data=.local)
#gSignalConnect(view,"key-release-event", ViewKeyRelease, data=.local)
gSignalConnect(view,"button-press-event", ViewButtonPress, data=.local)
gSignalConnect(view,"button-release-event", after=T, ViewButtonRelease, data=.local)
#gSignalConnect(view,"motion-notify-event", ViewMotionNotify)
gSignalConnect(view.rn,"key-press-event", RowNamesKeyPress, data=.local)
gSignalConnect(view.rn,"button-press-event", RowNamesButtonPress, data=.local)
#gSignalConnect(view.rn,"button-press-event", function(...) {print("called"); return(FALSE)}, data=.local)
# tree_view != NULL failed
gSignalConnect(view.rn, "focus-in-event", function(...) {
.local$draw.cursor <- FALSE
return(FALSE)
#gtkTreeSelectionUnselectAll(.local$ss)
})
gSignalConnect(view, "focus-in-event", function(...) {
.local$draw.cursor <- TRUE
gtkTreeSelectionUnselectAll(.local$ss.rn)
return(FALSE)
})
#gSignalConnect(view, "cursor-changed", ViewCursorChanged, data=.local)
rows.renderer <- view.rn$getColumns()[[1]]$getCellRenderers()[[1]]
.local$rows.renderer <- rows.renderer
gSignalConnect(rows.renderer,"edited", function(renderer, path, new.text){
idx <- as.integer(path)+1
if(new.text != row.names(.local$theFrame)[idx]){
task <- list(list(func="ChangeRowNames",
arg = list(theNames = new.text, row.idx=idx)))
DoTaskWrapper(.local, task)
}
return(FALSE)
})
gSignalConnect(view, "expose-event", after=T, ViewExpose, data=.local)
#gSignalConnect(view.rn, "expose-event", after=T, RowNamesExpose, data=.local)
gSignalConnect(view, "scroll-event", after=T, DoScrollUpdate, data=.local)
gSignalConnect(view.rn, "scroll-event", after=T, DoScrollUpdate, data=.local)
gSignalConnect(view, "leave-notify-event", AddHScrollTimeout, data=.local)
lapply(c("enter-notify-event", "button-release-event", "focus-out-event"),
function(evt) gSignalConnect(view, evt, RemoveHScrollTimeout, data=.local))
.local$viewGetBinWindow <- view$getBinWindow()
.local$view.rnGetBinWindow <- view.rn$getBinWindow()
.local$gc.invert <- gdkGCNew(.local$viewGetBinWindow)
.local$gc.invert$setFunction('GDK_INVERT')
.local$gc.cursor <- gdkGCNew(.local$viewGetBinWindow)
.local$gc.cursor$setLineAttributes(line.width=1, line.style=integer(3), cap.style=integer(0), join.style=integer(0))
toplevel <- .local$group.main$getToplevel()
if (toplevel$flags() & GtkWidgetFlags["toplevel"]){
.local$toplevel <- toplevel
}
# Kill drag and drop, it causes a crash
# http://www.mail-archive.com/gtk-app-devel-list@gnome.org/msg11623.html
screen <- .local$group.main$getScreen()
settings <- gtkSettingsGetForScreen(screen)
settings[["gtk-dnd-drag-threshold"]] <- 10000
checkPtrType(view, "GtkTreeView")
.local$scrollRowNames <- TRUE
sw.va.value.ch <- function(obj, data){
sw2 <- data$sw2
.local <- data$.local
if(!is.null(.local$scrollRowNames)
&& .local$scrollRowNames){
# Take over the event loop
# See http://wiki.laptop.org/go/PyGTK/Smooth_Animation_with_PyGTK
while(gtkEventsPending())
gtkMainIteration()
gtkAdjustmentSetValue(sw2, gtkAdjustmentGetValue(obj))
.local$allow.key.flag <- TRUE # For paging, however, this has a problem...
}
}
gSignalConnect(sw.view.va, "value-changed", sw.va.value.ch, data=list(sw2=sw.rn.va, .local=.local))
# for some reason the last column doesn't get alignment set properly
alignment <- gtkWidgetGetParent(allColumns[[length(allColumns)]]$eventbox)
gtkAlignmentSet(alignment, 0, 1, 1, 1)
return(TRUE)
}) # end of map event callback
return(gtktab)
} # end MakeDFEditWindow
ConvertToDataObj <- function(items){
.e <- new.env()
.e$items <- items
.e$dataset.attributes <- attributes(items)
if(is.ts(items)) {
items <- data.frame(x=as.numeric(items), check.names=FALSE)
} else {
tryCatch(.e$items <- data.frame(.e$items, check.names=FALSE, stringsAsFactors=F), error = function(e)
tryCatch(.e$items <- as.matrix(.e$items), error = function(e)
stop(paste("Passed a", paste(class(.e$items), collapse=", "), "but expected a valid data object"))))
}
rv <- list(items=.e$items, dataset.attributes = .e$dataset.attributes)
.e <- NULL
return(rv)
}
#' Data frame editor for RGtk2
#'
#' @param items A data frame (?) to display graphically
#' @param dataset.name Name for data set
#' @param container An RGtk2 container object to place widget within. (Can this be missing?)
#' @return An object of class GtkDfEdit for which a few RGtk2-style methods are defined
#' @export
gtkDfEdit <- function(items, dataset.name = deparse(substitute(items)),
size.request=c(600, 300), col.width = 64,
dataset.class="data.frame", editable = TRUE,
autosize = length(dim(items)) < 2 || ncol(items)<25,
update=TRUE,
envir=.GlobalEnv, ...){
# our local environment
#print(dim(.local$items))
.local <- new.env()
.local$items <- items
.local$dataset.name <- dataset.name
.local$dataset.class <- class(items)
stopifnot(editable %in% c(TRUE, FALSE))
.local$editable <- editable
stopifnot(autosize %in% c(TRUE, FALSE))
.local$autosize <- autosize
stopifnot(update %in% c(TRUE, FALSE))
.local$update.frame <- update
.local$dataset.attributes <- attributes(items)
#3-13-10
# have we been passed a string referring to an object?
# Might be "a", or "a$b$c"
if(!any(class(items)%in%DATA_OBJECTS)){
if (is.character(items) && nchar(items)){
.local$dataset.name <- items
tryCatch({ # try eval(), then get() just in case it's a name using "$"
.local$items <- safe.eval(items, envir=envir)
.local$dataset.class <- class(.local$items)
.local$dataset.attributes <- attributes(.local$items)
},
error = function(e) tryCatch({
.local$items <- safe.eval(items, envir=envir)
.local$dataset.class <- class(.local$items)
.local$dataset.attributes <- attributes(.local$items)
}, error = function(e)
stop("Passed a name that isn't a valid object") ) )
if(!any(class(.local$items)%in%DATA_OBJECTS)) {
.local$rv <- ConvertToDataObj(.local$items)
.local$items <- .local$rv$items; .local$dataset.attributes <- .local$rv$dataset.attributes
}
} else {
.local$rv <- ConvertToDataObj(items)
.local$items <- .local$rv$items; .local$dataset.attributes <- .local$rv$dataset.attributes
}
}
.local$theFrame <- MakeInternalDataFrame(.local$items, add.rows=EXTRA_ROW)
.local$items <- NULL
.local$theStack <- list()
.local$selectedColumns <- integer(0)
.local$gtktab <- MakeDFEditWindow(.local, .local$theFrame, size.request, col.width)
group.main <- gtkVBoxNew()
.local$group.main <- group.main
group.main$packStart(.local$gtktab, TRUE, TRUE, 0)
group.main$setData(".local", .local) # use getData
class(group.main) <- c("GtkDfEdit", class(group.main))
gSignalConnect(group.main, "map", function(...)
DoUserCall("OnLoad", list(sourc=dataset.name, typ="rdata"), .local)
)
# gSignalConnect(group.main, "destroy", function(obj, ...){
# print("Destroyed")
# #browser()
# .local <- obj$getData(".local")
# .local$model$setFrame()
# rm(list=ls(env=.local), envir=.local)
# obj$setData(".local", NULL)
## print(gc()[2,2])
# })
return(group.main)
}
#' get selected row and column indices
#'
#' @param object The RGtk2DfEdit object
#' @return the 1-indexed selected rows
#' @export
gtkDfEditGetSelection <- function(object){
.local <- object$getData(".local")
dmm <- object$getDimension()
columns=GetSelectedColumns(.local)
rows <- GetSelectedRows(.local$view, .local)
if(!length(rows) && !length(columns)){
rows <- GetSelectedRows(.local$view.rn, .local)
if(length(rows))
if(dmm[2]) columns <- 1:dmm[2]
} else if (!length(rows) && length(columns)){
if(dmm[1]) rows <- 1:dmm[1]
}
return(list(rows=rows, columns=columns))
}
## JV ADD METHODS
## Should be able to call these via RGtk2 dispatch: obj$getModel() instead of gtkDfEditGetModel(obj)
#' get Model from object
#'
#' @param object The RGtk2DfEdit object
#' @return the RGtk2DataFrame that is the backend model for the widget
#' @export
gtkDfEditGetModel <- function(object) {
object$getData(".local")$model
}
#' Return the dimensions (nrow, ncol) of the RGtk2DfEdit object
#'
#' @param object The RGtk2DfEdit object
#' @return Returns the number of rows and columns -- not counting row names
#' @export
gtkDfEditGetDimension <- function(object) {
dim(object$getModel()) - c(EXTRA_ROW,2) # rownames
}
#' Return the columns of the RGtk2DfEdit object
#'
#' @param object The RGtk2DfEdit object
#' @return Returns the column names for the current object
#' @export
gtkDfEditGetColumnNames <- function(object) {
model <- object$getModel()
nms <- colnames(model)[-1]
return(nms)
}
#' Return the row names of the RGtk2DfEdit object
#'
#' @param object The RGtk2DfEdit object
#' @return Returns the row names for the current object
#' @export
gtkDfEditGetRowNames <- function(object) {
model <- object$getModel()
nms <- model[,1, drop=TRUE]
return(nms)
}
#' Return a data frame from the RGtk2DfEdit object
#'
#' @param object The RGtk2DfEdit object
#' @return Returns the data frame with row names and column names
#' @export
gtkDfEditGetDataFrame <- function(object) {
.local <- object$getData(".local")
#dimnames(model) <- list(object$getRowNames(), object$getColumnNames())
return(MakeExternalDataFrame(object$getModel(), .local))
}
#' S3 data extraction method
#'
#' Grabs data frame then passes onto [.data.frame method
#' @method [ RGtkDfEdit
#' @param x The RGtk2DfEdit object
#' @param i Row index
#' @param j Column indext
#' @param drop passed to extraction for data frame
#' @return The extracted entries
#' @export
"[.GtkDfEdit" <- function(x, i, j, drop = TRUE) {
if(missing(drop))
if (missing(i)) {
drop <- TRUE
} else {
drop <- length(x$getDimension()[2]) == 1
}
df <- x$getDataFrame()
df[i,j, drop=drop]
}
gtkDfEditSetActionHandler <- function(object, func.name, handler=NULL, data=NULL) {
l <- object$getData(".local")
if(!length(l$changed.handler)) l$changed.handler <- list()
l$changed.handler[[func.name]]$func <- handler
l$changed.handler[[func.name]]$data <- data
object$setData(".local", l)
invisible()
}
# needs missing i, j methods
"[<-.GtkDfEdit" <- function(x, i, j, value) {
l <- x$getData(".local")
task <- list(
list(func="ChangeCells",
arg = list(nf=value, row.idx=i, col.idx=ToInternalColIdx(j), do.coercion=T)))
DoTaskWrapper(l, task)
x
}
gtkDfEditDoTask <- function(x, task){
# Correct col.idx to internal
if(length(task)){
for(jj in 1:length(task)){
stopifnot(all(c("func", "arg")%in%names(task[[jj]])))
if("col.idx"%in%names(task[[jj]]$arg))
task[[jj]]$arg$col.idx <- ToInternalColIdx(task[[jj]]$arg$col.idx)
if(task[[jj]]$func=="InsertRows")
task[[jj]]$arg$nf <- MakeInternalDataFrame(task[[jj]]$arg$nf, add.rows=F)
else if(task[[jj]]$func=="InsertColumns")
task[[jj]]$arg$nf <- MakeInternalDataFrame(task[[jj]]$arg$nf, add.columns=F)
}
DoTaskWrapper(x$getData(".local"), task)
}
}
gtkDfEditUndo <- function(x){
DoUndo(x$getData(".local"))
}
gtkDfEditSort <- function(x){
l <- x$getData(".local")
dd <- dim(l$theFrame)
DoSortDialog(l$theFrame[, -dd[2],drop=F], SortHandler, l)
}
gtkDfEditEditFactors <- function(x){
.local <- x$getData(".local")
DoFactorEditor(.local$theFrame, .local$toplevel, integer(0),
FactorEditorHandler, data=.local)
}
gtkDfEditCommandData <- function(x){
.local <- x$getData(".local")
sc <- GetSelectedColumns(.local)
if(length(sr <- GetSelectedRows(.local$view, .local))==0)
sr <- 1:(dim(.local$theFrame)[1]-1)
CommandData(.local, sr, sc)
}
gtkDfEditGetDatasetName <- function(x){
x$getData(".local")$dataset.name
}
gtkDfEditSetDatasetName <- function(x, new.name){
ModelChangeDatasetName(x$getData(".local"), new.name)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.