tags).
html <- gtkHTMLNew()
# class(html) <- gtkObjectGetClasses(html, FALSE)
html$AddCallback("object-requested", embeddedObjectHandler)
# a list of the previous files visited.
history <- character(0)
status <- NULL
# setBase() is getting the wrong arguments going to the
tmpBase <- getwd()
Load <-
# Loads the contents of a file into the HTML widget.
# Function so that we can call it from menu callbacks.
function(infile, reload = FALSE) {
if(!reload)
addToHistory(fileName)
if(file.exists(infile)) {
html$SetBase(infile)
tmpBase <<- dirname(infile)
lns <- paste(readLines(infile), collapse="\n")
html$LoadFromString(lns, -1)
fileName <<- infile
} else if(!is.null(status)) {
status$Push(statusContext, paste(infile,"doesn't exist"))
}
TRUE
}
addToHistory <-
# Add the file name to the history list
# and add it to the menu
function(name) {
if(!any(name == history)) {
HistoryMenu$Append(item <- gtkMenuItem(name))
item$AddCallback("activate", function(w, cmd) {Load(cmd, FALSE)}, data = name, object = FALSE)
}
history <<- c(history, name)
TRUE
}
sw <- gtkScrolledWindow(show = FALSE)
# Do not use with viewport. Seg-faults in layout allocation size!
sw$Add(html)
if(!createWindow) {
# Now actually load the file
Load(fileName, TRUE)
return(list(html = html, scrolledWindow = sw))
}
# create the window and set its size.
win <- gtkWindow(show = FALSE)
win$SetUsize(400, 500)
# Now create a vertical box and put the menu and the HTML widget (actually
# the scrolled window in wich it resides) into it.
box <- gtkVBox(FALSE, 4)
mbar <- gtkMenuBar()
fileMenu <- gtkMenuItem("File")
# Create a pull-down menu with a function in command.S
# Each of the entries has the same callback function given in the
# second argument.
m <- createSimpleMenu(c("Load", "Reload", "", "Edit", "", "Close"),
function(w, cmd) {
if(cmd == "Load") {
dlg <- gtkFileSelection("Load html file")
dlg[["OkButton"]]$AddCallback("clicked",
function(w) {
f <- dlg$GetFilename()
if(f != "") {
Load(f)
dlg$Destroy()
}
})
dlg[["CancelButton"]]$AddCallback("clicked", function(w) dlg$Destroy())
dlg$Show()
} else if(cmd == "Reload") {
Load(fileName, TRUE)
} else if(cmd == "Close") {
win$Hide()
}
})
fileMenu$SetSubmenu(m$menu)
mbar$Append(fileMenu)
# Create an empty history menu to which we will add the entries
# as we load new files and store the previous value
historyMenu <- gtkMenuItem("History")
HistoryMenu <- gtkMenu()
historyMenu$SetSubmenu(HistoryMenu)
mbar$Append(historyMenu)
# Put the menu bar into the top-level box.
box$PackStart(mbar, expand = FALSE)
# Now create a row in the box that has a URL label, an entry to type a URL
# and a button to process that entry if one doesn't want to use the Enter key.
urlBox <- gtkHBox(FALSE, 10)
urlBox$PackStart(gtkLabel("URL"), expand = FALSE, fill= FALSE)
urlEntry <- gtkEntry()
urlBox$PackStart(urlEntry, expand = TRUE)
# When the user hits return, load the URL!
urlEntry$AddCallback("activate", function(w) {
txt <- w$GetText()
if(txt != "") {
Load(txt)
}
})
btn <- gtkButton("Go")
urlBox$PackStart(btn, expand = FALSE, fill= FALSE)
btn$AddCallback("clicked", function(w) {
txt <- urlEntry$GetText()
if(txt != "")
Load(txt)
})
box$PackStart(urlBox, expand = FALSE)
# Put the scrolled window for the HTML widget next.
box$PackStart(sw)
# Now create status bar at the bottom of the window
# and use this to display URLs that we mouse-over.
status <- gtkStatusbar()
statusContext <- status$GetContextId("description")
box$PackEnd(status, expand= FALSE)
# Now actually load the file
Load(fileName, TRUE)
win$Add(box)
win$ShowAll()
# Now setup some handlers for the HTML actions.
# Callback for user clicking on a link.
link <- html$AddCallback("link-clicked",
function(w, link) {
print(link)
if(length(grep("^#", link))) {
html$JumpToAnchor(gsub("^#","", link))
} else if(length(grep("^R:", link))) {
eval(parse(text = substring(link, 3)))
} else {
if(file.exists(link)) {
Load(link)
} else if(file.exists(paste(tmpBase, link, sep=.Platform$file.sep)))
Load(paste(tmpBase, link, sep=.Platform$file.sep))
# cat("External Link:",link,"\n")
}
})
# These are for mouse-over links and we display the locations
# in the status bar.
link <- html$AddCallback("on-url",
function(w, link) {
if(link != "") {
status$Push(statusContext, link)
}
NULL
})
# Handler for when the user clicks on the form submit
submit <- html$AddCallback("submit",
function(w, str1, str2, str3) {
cmd <- parseFormURL(str3)
print(eval(parse(text = cmd["command"])))
})
invisible(list(win = win, html = html, callbackIds = list(link, submit)))
}
parseFormURL <-
#
# Take the string reported by an HTML form in the format
# name1=value1&name2=value2&name3=value3....&name-n=value-n
# and return a vector of the form
# c(name1=value, name2=value2,...)
# i.e. a named character vector with the values and indexed
# by the corresponding name.
#
function(url)
{
# Do the first level split at the &'s in the string.
cmd <- strsplit(url,"&")[[1]]
# Now split by the =
cmd <- strsplit(cmd,"=")
# Get the names and then the values in two separate steps.
#
name <- sapply(cmd, function(x) x[[1]])
cmd <- sapply(cmd, function(x) if(length(x) > 1) x[[2]] else "")
names(cmd) <- name
cmd
}
htmlOutputFilter <-
#
# An ``object'' that provides methods for generating
# HTML for an S session, giving a representation of
# a task (expression and result).
#
function()
{
# Counter for the number of commands that have been displayed.
ctr <- 0
markup <-
#
# Create a textual representation of the given
# command (`cmd') and result (`value').
#
function(cmd, value) {
ctr <<- ctr + 1
# Using a text connection is convenient, but
# having the result be stored in a global variable
# is one of the worst "design" decisions I have seen!
# In the future, this will be fixed.
con <- textConnection(".out", open="w")
sink(con)
on.exit({sink(); close(con)})
print(value) # print the value to the text connection so we
# we can get the resulting text next.
out <- get(".out")
val <- c("", ctr,
"",
paste("", sep=""),
cmd, " \n",
"\n\n",
out,
"\n \n")
val
}
header <-
# Called when starting the session
function() {
"Output \n"
}
footer <-
# Called each time we append an entry and should provide
# the closing element to finish off the document.
# This should provide the corresponding end elements for those
# in the header.
function() {
" "
}
# Return the filter methods as a list
# and give it a particular class for identification purposes.
m <- list(header = header,
markup = markup,
footer = footer,
getCurrentAnchor = function() as.character(ctr))
class(m) <- "HTMLCommandOutputFilter"
m
}
localOutput <-
#
# Simple interface with 2 HTML widgets stacked on top of each other
# The file displayed in the first is expected to present the interface
# via form so that when it is submitted there is an "command" variable
# in the data. This is parsed and evaluated and the result
# is displayed in the lower HTML widget. We markup the output
# using the `filter' which can do what it wants. In the default
# case, this makes the expression a link which if clicked re-runs
# that command. Also, it puts the result in a tag
# and generates the content as if it were printed by R.
#
function(fileName = "examples/form.html", filter = htmlOutputFilter())
{
# Create a basic HTML widget without the window
w <- viewHtml(fileName, createWindow = FALSE)
#
html <- w$html
w$scrolledWindow$SetUsize(500,400)
# Now create the window
win <- gtkWindow(show = FALSE)
win$SetUsize(500,600)
# Create a split pane that the user can adjust directly
# The top contains the HTML document with the form, and
# the bottom one shows the output from the commands.
pane <- gtkVPaned()
pane$Add(w$scrolledWindow)
# The output HTML widget for the commands.
output <- gtkHTMLNew()
# Will be done when the bindings are done.
# class(output) <- gtkObjectGetClasses(output, FALSE)
sw <- gtkScrolledWindow()
sw$Add(output)
pane$Add(sw)
# Get the header for the output page.
txt <- filter$header()
# When the user clicks on the submit button in form in the top HTML widget,
# process the settings and evaluate the `command' value.
submit <- html$AddCallback("submit",
function(w, str1, str2, str3) {
cmd <- parseFormURL(str3)
evalCommand(cmd["command"], show = as.logical(cmd["show"]))
})
# Arrange to re-evaluate the command when the user clicks on it
# in the output area.
output$AddCallback("link-clicked",
function(w, link) {
evalCommand(link, show = TRUE)
})
evalCommand <-
# Evaluate a command, put the command and result
# in the output window by transforming the {expression, value}
# pair through the filter.
function(cmd, show = TRUE) {
val <- eval(parse(text = cmd))
if(!show)
return(val)
# Now display the result, marking it up as we want.
txt <<- c(txt, filter$markup(cmd, val))
contents <- paste(txt, filter$footer(), collapse="\n", sep="")
output$LoadFromString(contents, nchar(contents))
# And scroll to this new entry.
output$JumpToAnchor(filter$getCurrentAnchor())
val
}
win$Add(pane)
win$ShowAll()
return(list(win = win, output = output, html = html))
}
embeddedObjectHandler <-
#
# called when an HTML widget needs to act on an
# tag for an embedded object.
# This returns TRUE or FALSE depending on whether it understood
# and processed the request or not.
# We can have built-in types or just evaluate the script
# in the init parameter
#
#
#
function(html, obj)
{
type <- obj[["Type"]]
if(type == "app/x-color") {
# Create a color selection dialog.
obj$Add(gtkColorSelection())
} else if(type == "app/x-button") {
# Create a button and a simple callback.
label <- obj$GetParameter("label")
if(label == "")
label <- "Embedded Widget"
btn <- gtkButton(label)
obj$Add(btn)
btn$AddCallback("clicked", function(w) {print("Callback from embedded widget")})
} else if(type == "app/x-slider") {
# Create a slider, using values from the parameters for the min, max and value
# if provided.
mn <- obj$GetParameter("min")
mn <- ifelse(mn != "", as.numeric(mn), 0)
mx <- obj$GetParameter("max")
mx <- ifelse(mn != "", as.numeric(mx), 0)
val <- obj$GetParameter("value")
val <- ifelse(val != "", as.numeric(val), mn)
adj <- gtkAdjustment(val, mn, mx, .1, 1, 1)
scale <- gtkHScale(adj)
obj$Add(scale)
} else if(type == "app/x-R-device") {
dev <- gtk(no.window = TRUE);
class(dev) <- gtkObjectGetClasses(dev, check=FALSE)
dev$Show()
box <- gtkHBox(TRUE,10)
box$PackStart(dev)
obj$Add(box)
} else {
# Check if there is an init paramter and if so,
# attempt to evaluate it as an S command/expression.
init <- obj$GetParameter("init")
if(init != "") {
val <- eval(parse(text = init))
# If the result is a GtkWidget, then add it to the
# embedded widget container.
add <- as.logical(obj$GetParameter("addWidget"))
if(inherits(val, "GtkWidget") && (is.na(add) || add == TRUE)) {
obj$Add(val)
val$Reparent(obj)
} else if(is.function(val)) {
val(html, obj)
}
} else {
# say that we did't handle this.
return(FALSE)
}
}
TRUE
}