R/GUI-selector.R

##' Select a subset of data through a GUI to be brushed
##'
##' We use a GUI created by \pkg{qtbase} to subset the data based on a
##' given categorical variable. Specifically, we choose certain values
##' of the variable (using the mouse or keyboard) and all the
##' observations which have the same values on this variable will be
##' brushed. This selector can link to any plots based on a mutaframe
##' created by \code{\link{qdata}}.
##'
##' The GUI supports multiple selections when we hold the Shift or
##' Ctrl key. If the character string in the text input box matches
##' with multiple items in the list, all of them will be selected.
##'
##' When we select items in the list, usually a plot based on the same
##' data will get brushed accordingly. On the other hand, when we
##' click on a plot, the corresponding items in the list will be
##' selected as well.
##' @inheritParams qbar
##' @param vars a character string or an integer as a column index, or
##' a variable name (without quotes): the variable to be displayed in
##' the data selector (if not specified, the first non-numeric
##' variable will be used; if all columns are numeric, the first
##' column will be used)
##' @return \code{NULL} (a GUI will pop up)
##' @author Yihui Xie and Jason Crowley
##' @seealso \code{\link{qdata}}
##' @export
##' @example inst/examples/record_selector-ex.R
record_selector = function(vars, data) {
    l = as.list(match.call()[-1])
    if (is.null(l$vars)) {
        vars = names(data)[!(sapply(as.data.frame(data), is.numeric))][1]
        if (is.na(vars))
            vars = names(data)[1]
    } else if (is.symbol(l$vars)) vars = as.character(l$vars)
    if (is.numeric(vars)) vars = names(data)[vars]
    ## vars should be of length 1
    x = data[, vars[1]]
    xx = as.data.frame(data[!duplicated(x), vars[1], drop = FALSE])

    # instantiate the window
    w = Qt$QWidget()
    w$setWindowTitle(paste("Record Selector:", vars))

    # setup the table and link the data model to it
    lst = Qt$QListView()
    lst$setSelectionMode(Qt$QAbstractItemView$ExtendedSelection)
    lst$setAlternatingRowColors(TRUE)
    model = qdataFrameModel(xx)
    lst$setModel(model)
    selModel = lst$selectionModel()

    change1 = change2 = FALSE
    # set up a handler to update the .brushed column when the index is
    # changed in the table
    qconnect(selModel, "selectionChanged", function(filler1, filler2) {
        if (change1) return()
        change2 <<- TRUE
        currLvls = sapply(selModel$selectedIndexes(),
                           function(i) i$data())
        selected(data) = (x %in% currLvls)
        change2 <<- FALSE
    })

    # set up a search bar and attach an auto-completer to it that is
    # populated with the levels of the variable in question
    le = Qt$QLineEdit()
    comp = Qt$QCompleter(as.character(xx[,1]))
    comp$setCaseSensitivity(Qt$Qt$CaseInsensitive)
    le$setCompleter(comp)

    select_items = function(idx) {
            sel <- Qt$QItemSelection(model$index(idx[1],0), model$index(idx[1],0))
            if ((n <- length(idx)) > 1) {
                for (i in 2:n) {
                    sel$select(model$index(idx[i],0),model$index(idx[i],0))
                    sel$select(model$index(idx[i],0),model$index(idx[i],0))
                }
            }
            selModel$select(sel,Qt$QItemSelectionModel$ClearAndSelect)
    }
    # set up a handler that will update the selection in the table when
    # return is pressed in the line edit field. clears selection if the
    # text doesn't match a level of the variable. updates .brushed
    qconnect(le, "returnPressed", function() {
        if (change1) return()
        change2 <<- TRUE
        idx = if (le$text != "") {
            grep(le$text, as.character(xx[, 1]))
        } else integer(0)

        if(length(idx) > 1) select_items(idx-1) else selModel$clear()
        change2 <<- FALSE
    })
    ## let the GUI respond to changes in .brushed too
    d.idx = add_listener(data, function(i, j) {
        if (change2) return()
        change1 <<- TRUE
        if (j == '.brushed') {
            idx = which(xx[, 1] %in% x[selected(data)])
            if (length(idx)) {
                select_items(idx-1)
                lst$scrollTo(model$index(idx[1], 0), Qt$QAbstractItemView$EnsureVisible)
            } else selModel$clear()
        }
        change1 <<- FALSE
    })
    qconnect(w, 'destroyed', function(x) {
        remove_listener(data, d.idx)
    })
    # set the layout of the widgets, and attach it to the window
    lyt = Qt$QVBoxLayout()
    lyt$addWidget(lst)
    lyt$addWidget(le)
    w$setLayout(lyt)

    # make the window visible
    w$show()
}
tengfei/objectWidgetsQt documentation built on May 31, 2019, 8:34 a.m.