#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.setcoords()` was renamed to [tk_set_coords()] to create a more
#' consistent API.
#' @inheritParams tk_set_coords
#' @keywords internal
#' @export
tkplot.setcoords <- function(tkp.id, coords) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.setcoords()", "tk_set_coords()")
  tk_set_coords(tkp.id = tkp.id, coords = coords)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.rotate()` was renamed to [tk_rotate()] to create a more
#' consistent API.
#' @inheritParams tk_rotate
#' @keywords internal
#' @export
tkplot.rotate <- function(tkp.id, degree = NULL, rad = NULL) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.rotate()", "tk_rotate()")
  tk_rotate(tkp.id = tkp.id, degree = degree, rad = rad)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.reshape()` was renamed to [tk_reshape()] to create a more
#' consistent API.
#' @inheritParams tk_reshape
#' @keywords internal
#' @export
tkplot.reshape <- function(tkp.id, newlayout, ..., params) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.reshape()", "tk_reshape()")
  tk_reshape(tkp.id = tkp.id, newlayout = newlayout, params = params, ...)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.off()` was renamed to [tk_off()] to create a more
#' consistent API.
#'
#' @keywords internal
#' @export
tkplot.off <- function() {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.off()", "tk_off()")
  tk_off()
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.getcoords()` was renamed to [tk_coords()] to create a more
#' consistent API.
#' @inheritParams tk_coords
#' @keywords internal
#' @export
tkplot.getcoords <- function(tkp.id, norm = FALSE) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.getcoords()", "tk_coords()")
  tk_coords(tkp.id = tkp.id, norm = norm)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.fit.to.screen()` was renamed to [tk_fit()] to create a more
#' consistent API.
#' @inheritParams tk_fit
#' @keywords internal
#' @export
tkplot.fit.to.screen <- function(tkp.id, width = NULL, height = NULL) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.fit.to.screen()", "tk_fit()")
  tk_fit(tkp.id = tkp.id, width = width, height = height)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.export.postscript()` was renamed to [tk_postscript()] to create a more
#' consistent API.
#' @inheritParams tk_postscript
#' @keywords internal
#' @export
tkplot.export.postscript <- function(tkp.id) {
  # nocov start
  lifecycle::deprecate_soft(
    "2.0.0",
    "tkplot.export.postscript()",
    "tk_postscript()"
  )
  tk_postscript(tkp.id = tkp.id)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.close()` was renamed to [tk_close()] to create a more
#' consistent API.
#' @inheritParams tk_close
#' @keywords internal
#' @export
tkplot.close <- function(tkp.id, window.close = TRUE) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.close()", "tk_close()")
  tk_close(tkp.id = tkp.id, window.close = window.close)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.center()` was renamed to [tk_center()] to create a more
#' consistent API.
#' @inheritParams tk_center
#' @keywords internal
#' @export
tkplot.center <- function(tkp.id) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.center()", "tk_center()")
  tk_center(tkp.id = tkp.id)
} # nocov end

#' Interactive plotting of graphs
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `tkplot.canvas()` was renamed to [tk_canvas()] to create a more
#' consistent API.
#' @inheritParams tk_canvas
#' @keywords internal
#' @export
tkplot.canvas <- function(tkp.id) {
  # nocov start
  lifecycle::deprecate_soft("2.0.0", "tkplot.canvas()", "tk_canvas()")
  tk_canvas(tkp.id = tkp.id)
} # nocov end
#   IGraph R package
#   Copyright (C) 2003-2012  Gabor Csardi <csardi.gabor@gmail.com>
#   334 Harvard street, Cambridge, MA 02139 USA
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
#   02110-1301 USA
#
###################################################################

###################################################################
# Internal variables
###################################################################

# the environment containing all the plots
.tkplot.env <- new.env()
assign(".next", 1, .tkplot.env)

###################################################################
# Main function
###################################################################

#' Interactive plotting of graphs
#'
#' `tkplot()` and its companion functions serve as an interactive graph
#' drawing facility. Not all parameters of the plot can be changed
#' interactively right now though, e.g. the colors of vertices, edges, and also
#' others have to be pre-defined.
#'
#' `tkplot()` is an interactive graph drawing facility. It is not very well
#' developed at this stage, but it should be still useful.
#'
#' It's handling should be quite straightforward most of the time, here are
#' some remarks and hints.
#'
#' There are different popup menus, activated by the right mouse button, for
#' vertices and edges. Both operate on the current selection if the vertex/edge
#' under the cursor is part of the selection and operate on the vertex/edge
#' under the cursor if it is not.
#'
#' One selection can be active at a time, either a vertex or an edge selection.
#' A vertex/edge can be added to a selection by holding the `control` key
#' while clicking on it with the left mouse button. Doing this again deselect
#' the vertex/edge.
#'
#' Selections can be made also from the "Select" menu. The "Select some
#' vertices" dialog allows to give an expression for the vertices to be
#' selected: this can be a list of numeric R expessions separated by commas,
#' like `1,2:10,12,14,15` for example. Similarly in the "Select some
#' edges" dialog two such lists can be given and all edges connecting a vertex
#' in the first list to one in the second list will be selected.
#'
#' In the color dialog a color name like 'orange' or RGB notation can also be
#' used.
#'
#' The `tkplot()` command creates a new Tk window with the graphical
#' representation of `graph`. The command returns an integer number, the
#' tkplot id. The other commands utilize this id to be able to query or
#' manipulate the plot.
#'
#' `tk_close()` closes the Tk plot with id `tkp.id`.
#'
#' `tk_off()` closes all Tk plots.
#'
#' `tk_fit()` fits the plot to the given rectangle
#' (`width` and `height`), if some of these are `NULL` the
#' actual physical width od height of the plot window is used.
#'
#' `tk_reshape()` applies a new layout to the plot, its optional
#' parameters will be collected to a list analogous to `layout.par`.
#'
#' `tk_postscript()` creates a dialog window for saving the plot
#' in postscript format.
#'
#' `tk_canvas()` returns the Tk canvas object that belongs to a graph
#' plot. The canvas can be directly manipulated then, e.g. labels can be added,
#' it could be saved to a file programmatically, etc. See an example below.
#'
#' `tk_coords()` returns the coordinates of the vertices in a matrix.
#' Each row corresponds to one vertex.
#'
#' `tk_set_coords()` sets the coordinates of the vertices. A two-column
#' matrix specifies the new positions, with each row corresponding to a single
#' vertex.
#'
#' `tk_center()` shifts the figure to the center of its plot window.
#'
#' `tk_rotate()` rotates the figure, its parameter can be given either
#' in degrees or in radians.
#'
#' tkplot.center tkplot.rotate
#' @param graph The `graph` to plot.
#' @param canvas.width,canvas.height The size of the tkplot drawing area.
#' @param tkp.id The id of the tkplot window to close/reshape/etc.
#' @param window.close Leave this on the default value.
#' @param width The width of the rectangle for generating new coordinates.
#' @param height The height of the rectangle for generating new coordinates.
#' @param newlayout The new layout, see the `layout` parameter of tkplot.
#' @param norm Logical, should we norm the coordinates.
#' @param coords Two-column numeric matrix, the new coordinates of the
#'   vertices, in absolute coordinates.
#' @param degree The degree to rotate the plot.
#' @param rad The degree to rotate the plot, in radian.
#' @param \dots Additional plotting parameters. See [igraph.plotting] for
#'   the complete list.
#' @return `tkplot()` returns an integer, the id of the plot, this can be
#'   used to manipulate it from the command line.
#'
#'   `tk_canvas()` returns `tkwin` object, the Tk canvas.
#'
#'   `tk_coords()` returns a matrix with the coordinates.
#'
#'   `tk_close()`, `tk_off()`, `tk_fit()`,
#'   `tk_reshape()`, `tk_postscript()`, `tk_center()`
#'   and `tk_rotate()` return `NULL` invisibly.
#' @author Gabor Csardi \email{csardi.gabor@@gmail.com}
#' @seealso [plot.igraph()], [layout()]
#' @family tkplot
#' @export
#' @keywords graphs
#' @section Examples:
#' \preformatted{
#' g <- make_ring(10)
#' tkplot(g)
#'
#' ## Saving a tkplot() to a file programmatically
#' g <- make_star(10, center=10) %u% make_ring(9, directed=TRUE)
#' E(g)$width <- sample(1:10, ecount(g), replace=TRUE)
#' lay <- layout_nicely(g)
#'
#' id <- tkplot(g, layout=lay)
#' canvas <- tk_canvas(id)
#' tcltk::tkpostscript(canvas, file="/tmp/output.eps")
#' tk_close(id)
#'
#' ## Setting the coordinates and adding a title label
#' g <- make_ring(10)
#' id <- tkplot(make_ring(10), canvas.width=450, canvas.height=500)
#'
#' canvas <- tk_canvas(id)
#' padding <- 20
#' coords <- norm_coords(layout_in_circle(g), 0+padding, 450-padding,
#'                       50+padding, 500-padding)
#' tk_set_coords(id, coords)
#'
#' width <- as.numeric(tkcget(canvas, "-width"))
#' height <- as.numeric(tkcget(canvas, "-height"))
#' tkcreate(canvas, "text", width/2, 25, text="My title",
#'          justify="center", font=tcltk::tkfont.create(family="helvetica",
#'          size=20,weight="bold"))
#' }
#'
tkplot <- function(graph, canvas.width = 450, canvas.height = 450, ...) {
  # nocov start
  ensure_igraph(graph)

  # Libraries
  requireNamespace("tcltk", quietly = TRUE) ||
    stop("tcl/tk library not available")

  params <- i.parse.plot.params(graph, list(...))

  # Use the palette specified by the user (if any)
  palette <- params("plot", "palette")
  if (!is.null(palette)) {
    old_palette <- palette(palette)
    on.exit(palette(old_palette), add = TRUE)
  }

  # Visual parameters
  labels <- params("vertex", "label")
  label.color <- .tkplot.convert.color(params("vertex", "label.color"))
  label.font <- .tkplot.convert.font(
    params("vertex", "label.font"),
    params("vertex", "label.family"),
    params("vertex", "label.cex")
  )
  label.degree <- params("vertex", "label.degree")
  label.dist <- params("vertex", "label.dist")
  vertex.color <- .tkplot.convert.color(params("vertex", "color"))
  vertex.size <- params("vertex", "size")

  # Adjusting size
  vertex.size <- i.rescale.vertex(
    vertex.size,
    c(-canvas.width, canvas.height) / 2,
    params("vertex", "relative.size")
  )

  vertex.frame.color <- .tkplot.convert.color(params("vertex", "frame.color"))

  edge.color <- .tkplot.convert.color(params("edge", "color"))
  edge.width <- params("edge", "width")
  edge.labels <- params("edge", "label")
  edge.lty <- params("edge", "lty")
  loop.angle <- params("edge", "loop.angle")
  arrow.mode <- params("edge", "arrow.mode")
  edge.label.font <- .tkplot.convert.font(
    params("edge", "label.font"),
    params("edge", "label.family"),
    params("edge", "label.cex")
  )
  edge.label.color <- params("edge", "label.color")
  arrow.size <- params("edge", "arrow.size")[1]
  curved <- params("edge", "curved")
  curved <- rep(curved, length.out = ecount(graph))

  layout <- unname(params("plot", "layout"))
  layout[, 2] <- -layout[, 2]
  margin <- params("plot", "margin")
  margin <- rep(margin, length.out = 4)

  # the new style parameters can't do this yet
  arrow.mode <- i.get.arrow.mode(graph, arrow.mode)

  # Edge line type
  edge.lty <- i.tkplot.get.edge.lty(edge.lty)

  # Create window & canvas
  top <- tcltk::tktoplevel(background = "lightgrey")
  canvas <- tcltk::tkcanvas(
    top,
    relief = "raised",
    width = canvas.width,
    height = canvas.height,
    borderwidth = 2
  )
  tcltk::tkpack(canvas, fill = "both", expand = 1)

  # Create parameters
  vertex.params <- sdf(
    vertex.color = vertex.color,
    vertex.size = vertex.size,
    label.font = label.font,
    NROW = vcount(graph)
  )

  params <- list(
    vertex.params = vertex.params,
    edge.color = edge.color,
    label.color = label.color,
    labels.state = 1,
    edge.width = edge.width,
    padding = margin * 300 + max(vertex.size) + 5,
    grid = 0,
    label.degree = label.degree,
    label.dist = label.dist,
    edge.labels = edge.labels,
    vertex.frame.color = vertex.frame.color,
    loop.angle = loop.angle,
    edge.lty = edge.lty,
    arrow.mode = arrow.mode,
    edge.label.font = edge.label.font,
    edge.label.color = edge.label.color,
    arrow.size = arrow.size,
    curved = curved
  )

  # The popup menu
  popup.menu <- tcltk::tkmenu(canvas)
  tcltk::tkadd(
    popup.menu,
    "command",
    label = "Fit to screen",
    command = function() {
      tk_fit(tkp.id)
    }
  )

  # Different popup menu for vertices
  vertex.popup.menu <- tcltk::tkmenu(canvas)
  tcltk::tkadd(
    vertex.popup.menu,
    "command",
    label = "Vertex color",
    command = function() {
      tkp <- .tkplot.get(tkp.id)
      vids <- .tkplot.get.selected.vertices(tkp.id)
      if (length(vids) == 0) {
        return(FALSE)
      }

      initialcolor <- tkp$params$vertex.params[vids[1], "vertex.color"]
      color <- .tkplot.select.color(initialcolor)
      if (color == "") {
        return(FALSE)
      } # Cancel

      .tkplot.update.vertex.color(tkp.id, vids, color)
    }
  )
  tcltk::tkadd(
    vertex.popup.menu,
    "command",
    label = "Vertex size",
    command = function() {
      tkp <- .tkplot.get(tkp.id)
      vids <- .tkplot.get.selected.vertices(tkp.id)
      if (length(vids) == 0) {
        return(FALSE)
      }

      initialsize <- tkp$params$vertex.params[1, "vertex.size"]
      size <- .tkplot.select.number("Vertex size", initialsize, 1, 20)
      if (is.na(size)) {
        return(FALSE)
      }

      .tkplot.update.vertex.size(tkp.id, vids, size)
    }
  )

  # Different popup menu for edges
  edge.popup.menu <- tcltk::tkmenu(canvas)
  tcltk::tkadd(
    edge.popup.menu,
    "command",
    label = "Edge color",
    command = function() {
      tkp <- .tkplot.get(tkp.id)
      eids <- .tkplot.get.selected.edges(tkp.id)
      if (length(eids) == 0) {
        return(FALSE)
      }

      initialcolor <- ifelse(
        length(tkp$params$edge.color) > 1,
        tkp$params$edge.color[eids[1]],
        tkp$params$edge.color
      )
      color <- .tkplot.select.color(initialcolor)
      if (color == "") {
        return(FALSE)
      } # Cancel

      .tkplot.update.edge.color(tkp.id, eids, color)
    }
  )
  tcltk::tkadd(
    edge.popup.menu,
    "command",
    label = "Edge width",
    command = function() {
      tkp <- .tkplot.get(tkp.id)
      eids <- .tkplot.get.selected.edges(tkp.id)
      if (length(eids) == 0) {
        return(FALSE)
      }

      initialwidth <- ifelse(
        length(tkp$params$edge.width) > 1,
        tkp$params$edge.width[eids[1]],
        tkp$params$edge.width
      )
      width <- .tkplot.select.number("Edge width", initialwidth, 1, 10)
      if (is.na(width)) {
        return(FALSE)
      } # Cancel

      .tkplot.update.edge.width(tkp.id, eids, width)
    }
  )

  # Create plot object
  tkp <- list(
    top = top,
    canvas = canvas,
    graph = graph,
    coords = layout,
    labels = labels,
    params = params,
    popup.menu = popup.menu,
    vertex.popup.menu = vertex.popup.menu,
    edge.popup.menu = edge.popup.menu
  )
  tkp.id <- .tkplot.new(tkp)
  tcltk::tktitle(top) <- paste("Graph plot", as.character(tkp.id))

  # The main pull-down menu
  main.menu <- tcltk::tkmenu(top)
  tcltk::tkadd(main.menu, "command", label = "Close", command = function() {
    tk_close(tkp.id, TRUE)
  })
  select.menu <- .tkplot.select.menu(tkp.id, main.menu)
  tcltk::tkadd(main.menu, "cascade", label = "Select", menu = select.menu)
  layout.menu <- .tkplot.layout.menu(tkp.id, main.menu)
  tcltk::tkadd(main.menu, "cascade", label = "Layout", menu = layout.menu)
  view.menu <- tcltk::tkmenu(main.menu)
  tcltk::tkadd(main.menu, "cascade", label = "View", menu = view.menu)
  tcltk::tkadd(
    view.menu,
    "command",
    label = "Fit to screen",
    command = function() {
      tk_fit(tkp.id)
    }
  )
  tcltk::tkadd(
    view.menu,
    "command",
    label = "Center on screen",
    command = function() {
      tk_center(tkp.id)
    }
  )
  tcltk::tkadd(view.menu, "separator")
  view.menu.labels <- tcltk::tclVar(1)
  view.menu.grid <- tcltk::tclVar(0)
  tcltk::tkadd(
    view.menu,
    "checkbutton",
    label = "Labels",
    variable = view.menu.labels,
    command = function() {
      .tkplot.toggle.labels(tkp.id)
    }
  )
  # grid canvas object not implemented in tcltk (?) :(
  #   tcltk::tkadd(view.menu, "checkbutton", label="Grid",
  #         variable=view.menu.grid, command=function() {
  #           .tkplot.toggle.grid(tkp.id)})
  tcltk::tkadd(view.menu, "separator")
  rotate.menu <- tcltk::tkmenu(view.menu)
  tcltk::tkadd(view.menu, "cascade", label = "Rotate", menu = rotate.menu)
  sapply(
    c(-90, -45, -15, -5, -1, 1, 5, 15, 45, 90),
    function(deg) {
      tcltk::tkadd(
        rotate.menu,
        "command",
        label = paste(deg, "degree"),
        command = function() {
          tk_rotate(tkp.id, degree = deg)
        }
      )
    }
  )
  export.menu <- tcltk::tkmenu(main.menu)
  tcltk::tkadd(main.menu, "cascade", label = "Export", menu = export.menu)
  tcltk::tkadd(
    export.menu,
    "command",
    label = "Postscript",
    command = function() {
      tk_postscript(tkp.id)
    }
  )
  tcltk::tkconfigure(top, "-menu", main.menu)

  # plot it
  .tkplot.create.edges(tkp.id)
  .tkplot.create.vertices(tkp.id)
  # we would need an update here
  tk_fit(tkp.id, canvas.width, canvas.height)

  # Kill myself if window was closed
  tcltk::tkbind(top, "<Destroy>", function() tk_close(tkp.id, FALSE))

  ###################################################################
  # The callbacks for interactive editing
  ###################################################################

  tcltk::tkitembind(canvas, "vertex||label||edge", "<1>", function(x, y) {
    tkp <- .tkplot.get(tkp.id)
    canvas <- .tkplot.get(tkp.id, "canvas")
    .tkplot.deselect.all(tkp.id)
    .tkplot.select.current(tkp.id)
    #     tcltk::tkitemraise(canvas, "current")
  })
  tcltk::tkitembind(
    canvas,
    "vertex||label||edge",
    "<Control-1>",
    function(x, y) {
      canvas <- .tkplot.get(tkp.id, "canvas")
      curtags <- as.character(tcltk::tkgettags(canvas, "current"))
      seltags <- as.character(tcltk::tkgettags(canvas, "selected"))
      if ("vertex" %in% curtags && "vertex" %in% seltags) {
        if ("selected" %in% curtags) {
          .tkplot.deselect.current(tkp.id)
        } else {
          .tkplot.select.current(tkp.id)
        }
      } else if ("edge" %in% curtags && "edge" %in% seltags) {
        if ("selected" %in% curtags) {
          .tkplot.deselect.current(tkp.id)
        } else {
          .tkplot.select.current(tkp.id)
        }
      } else if ("label" %in% curtags && "vertex" %in% seltags) {
        vtag <- curtags[pmatch("v-", curtags)]
        tkid <- as.numeric(tcltk::tkfind(
          canvas,
          "withtag",
          paste(sep = "", "vertex&&", vtag)
        ))
        vtags <- as.character(tcltk::tkgettags(canvas, tkid))
        if ("selected" %in% vtags) {
          .tkplot.deselect.vertex(tkp.id, tkid)
        } else {
          .tkplot.select.vertex(tkp.id, tkid)
        }
      } else {
        .tkplot.deselect.all(tkp.id)
        .tkplot.select.current(tkp.id)
      }
    }
  )
  tcltk::tkitembind(
    canvas,
    "vertex||edge||label",
    "<Shift-Alt-1>",
    function(x, y) {
      canvas <- .tkplot.get(tkp.id, "canvas")
      tcltk::tkitemlower(canvas, "current")
    }
  )
  tcltk::tkitembind(canvas, "vertex||edge||label", "<Alt-1>", function(x, y) {
    canvas <- .tkplot.get(tkp.id, "canvas")
    tcltk::tkitemraise(canvas, "current")
  })
  tcltk::tkbind(canvas, "<3>", function(x, y) {
    canvas <- .tkplot.get(tkp.id, "canvas")
    tags <- as.character(tcltk::tkgettags(canvas, "current"))
    if ("label" %in% tags) {
      vtag <- tags[pmatch("v-", tags)]
      vid <- as.character(tcltk::tkfind(
        canvas,
        "withtag",
        paste(sep = "", "vertex&&", vtag)
      ))
      tags <- as.character(tcltk::tkgettags(canvas, vid))
    }
    if ("selected" %in% tags) {
      # The selection is active
    } else {
      # Delete selection, single object
      .tkplot.deselect.all(tkp.id)
      .tkplot.select.current(tkp.id)
    }
    tags <- as.character(tcltk::tkgettags(canvas, "selected"))
    ## TODO: what if different types of objects are selected
    if ("vertex" %in% tags || "label" %in% tags) {
      menu <- .tkplot.get(tkp.id, "vertex.popup.menu")
    } else if ("edge" %in% tags) {
      menu <- .tkplot.get(tkp.id, "edge.popup.menu")
    } else {
      menu <- .tkplot.get(tkp.id, "popup.menu")
    }
    x <- as.integer(x) + as.integer(tcltk::tkwinfo("rootx", canvas))
    y <- as.integer(y) + as.integer(tcltk::tkwinfo("rooty", canvas))
    tcltk::.Tcl(paste("tk_popup", tcltk::.Tcl.args(menu, x, y)))
  })
  if (tkp$params$label.dist == 0) {
    tobind <- "vertex||label"
  } else {
    tobind <- "vertex"
  }
  tcltk::tkitembind(canvas, tobind, "<B1-Motion>", function(x, y) {
    tkp <- .tkplot.get(tkp.id)
    x <- as.numeric(x)
    y <- as.numeric(y)
    width <- as.numeric(tcltk::tkwinfo("width", tkp$canvas))
    height <- as.numeric(tcltk::tkwinfo("height", tkp$canvas))
    if (x < 10) {
      x <- 10
    }
    if (x > width - 10) {
      x <- width - 10
    }
    if (y < 10) {
      y <- 10
    }
    if (y > height - 10) {
      y <- height - 10
    }

    # get the id
    tags <- as.character(tcltk::tkgettags(tkp$canvas, "selected"))
    id <- as.numeric(strsplit(tags[pmatch("v-", tags)], "-", fixed = TRUE)[[1]][
      2
    ])
    if (is.na(id)) {
      return()
    }
    # move the vertex
    .tkplot.set.vertex.coords(tkp.id, id, x, y)
    .tkplot.update.vertex(tkp.id, id, x, y)
  })
  if (tkp$params$label.dist != 0) {
    tcltk::tkitembind(canvas, "label", "<B1-Motion>", function(x, y) {
      tkp <- .tkplot.get(tkp.id)
      x <- as.numeric(x)
      y <- as.numeric(y)
      # get the id
      tags <- as.character(tcltk::tkgettags(tkp$canvas, "selected"))
      id <- as.numeric(strsplit(tags[pmatch("v-", tags)], "-", fixed = TRUE)[[
        1
      ]][2])
      if (is.na(id)) {
        return()
      }
      phi <- pi + atan2(tkp$coords[id, 2] - y, tkp$coords[id, 1] - x)
      .tkplot.set.label.degree(tkp.id, id, phi)
      .tkplot.update.label(tkp.id, id, tkp$coords[id, 1], tkp$coords[id, 2])
    })
  }

  # We don't need these any more, they are stored in the environment
  rm(
    tkp,
    params,
    layout,
    vertex.color,
    edge.color,
    top,
    canvas,
    main.menu,
    layout.menu,
    view.menu,
    export.menu,
    label.font,
    label.degree,
    vertex.frame.color,
    vertex.params
  )

  tkp.id
  # nocov end
}

###################################################################
# Internal functions handling data about layouts for the GUI
###################################################################
# nocov start
.tkplot.addlayout <- function(name, layout.data) {
  if (!exists(".layouts", envir = .tkplot.env)) {
    assign(".layouts", list(), .tkplot.env)
  }

  assign("tmp", layout.data, .tkplot.env)
  cmd <- paste(sep = "", ".layouts[[\"", name, "\"]]", " <- tmp")
  eval(parse(text = cmd), .tkplot.env)
  rm("tmp", envir = .tkplot.env)
}

.tkplot.getlayout <- function(name) {
  cmd <- paste(sep = "", ".layouts[[\"", name, "\"]]")
  eval(parse(text = cmd), .tkplot.env)
}

.tkplot.layouts.newdefaults <- function(name, defaults) {
  assign("tmp", defaults, .tkplot.env)
  for (i in seq(along.with = defaults)) {
    cmd <- paste(
      sep = "",
      '.layouts[["',
      name,
      '"]]$params[[',
      i,
      "]]$default <- tmp[[",
      i,
      "]]"
    )
    eval(parse(text = cmd), .tkplot.env)
  }
}

.tkplot.getlayoutlist <- function() {
  eval(parse(text = "names(.layouts)"), .tkplot.env)
}

.tkplot.getlayoutname <- function(name) {
  cmd <- paste(sep = "", '.layouts[["', name, '"]]$name')
  eval(parse(text = cmd), .tkplot.env)
}

.tkplot.addlayout(
  "random",
  list(name = "Random", f = layout_randomly, params = list())
)
.tkplot.addlayout(
  "circle",
  list(name = "Circle", f = layout_in_circle, params = list())
)
.tkplot.addlayout(
  "fruchterman.reingold",
  list(
    name = "Fruchterman-Reingold",
    f = layout_with_fr,
    params = list(
      niter = list(
        name = "Number of iterations",
        type = "numeric",
        default = 500
      ),
      start.temp = list(
        name = "Start temperature",
        type = "expression",
        default = expression(sqrt(vcount(.tkplot.g)))
      )
    )
  )
)
.tkplot.addlayout(
  "kamada.kawai",
  list(
    name = "Kamada-Kawai",
    f = layout_with_kk,
    params = list(
      maxiter = list(
        name = "Maximum number of iterations",
        type = "expression",
        default = expression(50 * vcount(.tkplot.g))
      ),
      kkconst = list(
        name = "Vertex attraction constant",
        type = "expression",
        default = expression(vcount(.tkplot.g))
      )
    )
  )
)
.tkplot.addlayout(
  "reingold.tilford",
  list(
    names = "Reingold-Tilford",
    f = layout_as_tree,
    params = list(
      root = list(
        name = "Root vertex",
        type = "numeric",
        default = 1
      )
    )
  )
)

.tkplot.addlayout(
  "davidson.harel",
  list(
    name = "Davidson-Harel",
    f = layout_with_dh,
    params = list(
      maxiter = list(
        name = "Maximum iterations",
        type = "numeric",
        default = 10
      ),
      fineiter = list(
        name = "Fine-tuning iterations",
        type = "expression",
        default = expression(max(10, log2(vcount(.tkplot.g))))
      ),
      cool.fact = list(
        name = "Cooling factor",
        type = "numeric",
        default = 0.75
      ),
      weight.node.dist = list(
        name = "Node distance weight",
        type = "numeric",
        default = 1.0
      ),
      weight.border = list(
        name = "Border weight",
        type = "numeric",
        default = 0.0
      ),
      weight.edge.lengths = list(
        name = "Edge length weight",
        type = "expression",
        default = expression(edge_density(.tkplot.g) / 10)
      ),
      weight.edge.crossings = list(
        name = "Edge crossing weight",
        type = "expression",
        default = expression(1.0 - sqrt(edge_density(.tkplot.g)))
      ),
      weight.node.edge.dist = list(
        name = "Node-edge distance weight",
        type = "expression",
        default = expression(0.2 * (1 - edge_density(.tkplot.g)))
      )
    )
  )
)

.tkplot.addlayout(
  "drl",
  list(
    name = "DrL/VxOrd",
    f = layout_with_drl,
    params = list(
      options = list(
        name = "Layout options",
        type = "choice",
        values = c("default", "coarsen", "coarsest", "refine"),
        default = "default"
      ),
      seed = list(
        name = "Random seed",
        type = "numeric",
        default = -1
      ),
      weights = list(
        name = "Edge weights",
        type = "expression",
        default = expression(NULL)
      ),
      fixed = list(
        name = "Fixed vertices",
        type = "expression",
        default = expression(NULL)
      ),
      dim = list(
        name = "Dimensions",
        type = "numeric",
        default = 2
      )
    )
  )
)
# nocov end
###################################################################
# Other public functions, misc.
###################################################################

#' @rdname tkplot
#' @export
tk_close <- function(tkp.id, window.close = TRUE) {
  # nocov start
  if (window.close) {
    cmd <- paste(sep = "", "tkp.", tkp.id, "$top")
    top <- eval(parse(text = cmd), .tkplot.env)
    tcltk::tkbind(top, "<Destroy>", "")
    tcltk::tkdestroy(top)
  }
  cmd <- paste(sep = "", "tkp.", tkp.id)
  rm(list = cmd, envir = .tkplot.env)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_off <- function() {
  # nocov start
  eapply(.tkplot.env, function(tkp) {
    tcltk::tkdestroy(tkp$top)
  })
  rm(list = ls(.tkplot.env), envir = .tkplot.env)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_fit <- function(tkp.id, width = NULL, height = NULL) {
  # nocov start
  tkp <- .tkplot.get(tkp.id)
  if (is.null(width)) {
    width <- as.numeric(tcltk::tkwinfo("width", tkp$canvas))
  }
  if (is.null(height)) {
    height <- as.numeric(tcltk::tkwinfo("height", tkp$canvas))
  }
  coords <- .tkplot.get(tkp.id, "coords")
  # Shift to zero
  coords[, 1] <- coords[, 1] - min(coords[, 1])
  coords[, 2] <- coords[, 2] - min(coords[, 2])
  # Scale
  coords[, 1] <- coords[, 1] /
    max(coords[, 1]) *
    (width - (tkp$params$padding[2] + tkp$params$padding[4]))
  coords[, 2] <- coords[, 2] /
    max(coords[, 2]) *
    (height - (tkp$params$padding[1] + tkp$params$padding[3]))
  # Padding
  coords[, 1] <- coords[, 1] + tkp$params$padding[2]
  coords[, 2] <- coords[, 2] + tkp$params$padding[3]
  # Store
  .tkplot.set(tkp.id, "coords", coords)
  # Update
  .tkplot.update.vertices(tkp.id)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_center <- function(tkp.id) {
  # nocov start
  tkp <- .tkplot.get(tkp.id)
  width <- as.numeric(tcltk::tkwinfo("width", tkp$canvas))
  height <- as.numeric(tcltk::tkwinfo("height", tkp$canvas))
  coords <- .tkplot.get(tkp.id, "coords")
  canvas.center.x <- width / 2
  canvas.center.y <- height / 2
  coords <- .tkplot.get(tkp.id, "coords")
  r1 <- range(coords[, 1])
  r2 <- range(coords[, 2])
  coords.center.x <- (r1[1] + r1[2]) / 2
  coords.center.y <- (r2[1] + r2[2]) / 2
  # Shift to center
  coords[, 1] <- coords[, 1] + canvas.center.x - coords.center.x
  coords[, 2] <- coords[, 2] + canvas.center.y - coords.center.y
  # Store
  .tkplot.set(tkp.id, "coords", coords)
  # Update
  .tkplot.update.vertices(tkp.id)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @param params Extra parameters in a list, to pass to the layout function.
#' @export
tk_reshape <- function(tkp.id, newlayout, ..., params) {
  # nocov start
  tkp <- .tkplot.get(tkp.id)
  new_coords <- do_call(
    newlayout,
    .args = c(list(tkp$graph), list(...), params)
  )
  .tkplot.set(tkp.id, "coords", new_coords)
  tk_fit(tkp.id)
  .tkplot.update.vertices(tkp.id)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_postscript <- function(tkp.id) {
  # nocov start
  tkp <- .tkplot.get(tkp.id)

  filename <- tcltk::tkgetSaveFile(
    initialfile = "Rplots.eps",
    defaultextension = "eps",
    title = "Export graph to PostScript file"
  )
  tcltk::tkpostscript(tkp$canvas, file = filename)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_coords <- function(tkp.id, norm = FALSE) {
  # nocov start
  coords <- .tkplot.get(tkp.id, "coords")
  coords[, 2] <- max(coords[, 2]) - coords[, 2]
  if (norm) {
    # Shift
    coords[, 1] <- coords[, 1] - min(coords[, 1])
    coords[, 2] <- coords[, 2] - min(coords[, 2])
    # Scale
    coords[, 1] <- coords[, 1] / max(coords[, 1]) - 0.5
    coords[, 2] <- coords[, 2] / max(coords[, 2]) - 0.5
  }
  coords
  # nocov end
}

#' @rdname tkplot
#' @export
tk_set_coords <- function(tkp.id, coords) {
  # nocov start
  stopifnot(is.matrix(coords), ncol(coords) == 2)
  .tkplot.set(tkp.id, "coords", coords)
  .tkplot.update.vertices(tkp.id)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_rotate <- function(tkp.id, degree = NULL, rad = NULL) {
  # nocov start
  coords <- .tkplot.get(tkp.id, "coords")

  if (is.null(degree) && is.null(rad)) {
    rad <- pi / 2
  } else if (is.null(rad) && !is.null(degree)) {
    rad <- degree / 180 * pi
  }

  center <- c(mean(range(coords[, 1])), mean(range(coords[, 2])))
  phi <- atan2(coords[, 2] - center[2], coords[, 1] - center[1])
  r <- sqrt((coords[, 1] - center[1])**2 + (coords[, 2] - center[2])**2)

  phi <- phi + rad

  coords[, 1] <- r * cos(phi)
  coords[, 2] <- r * sin(phi)

  .tkplot.set(tkp.id, "coords", coords)
  tk_center(tkp.id)
  invisible(NULL)
  # nocov end
}

#' @rdname tkplot
#' @export
tk_canvas <- function(tkp.id) {
  # nocov start
  .tkplot.get(tkp.id)$canvas
  # nocov end
}

###################################################################
# Internal functions, handling the internal environment
###################################################################

.tkplot.new <- function(tkp) {
  # nocov start
  id <- get(".next", .tkplot.env)
  assign(".next", id + 1, .tkplot.env)
  assign("tmp", tkp, .tkplot.env)
  cmd <- paste("tkp.", id, "<- tmp", sep = "")
  eval(parse(text = cmd), .tkplot.env)
  rm("tmp", envir = .tkplot.env)
  id
  # nocov end
}

.tkplot.get <- function(tkp.id, what = NULL) {
  # nocov start
  if (is.null(what)) {
    get(paste("tkp.", tkp.id, sep = ""), .tkplot.env)
  } else {
    cmd <- paste("tkp.", tkp.id, "$", what, sep = "")
    eval(parse(text = cmd), .tkplot.env)
  }
  # nocov end
}

.tkplot.set <- function(tkp.id, what, value) {
  # nocov start
  assign("tmp", value, .tkplot.env)
  cmd <- paste(sep = "", "tkp.", tkp.id, "$", what, "<-tmp")
  eval(parse(text = cmd), .tkplot.env)
  rm("tmp", envir = .tkplot.env)
  TRUE
  # nocov end
}

.tkplot.set.params <- function(tkp.id, what, value) {
  # nocov start
  assign("tmp", value, .tkplot.env)
  cmd <- paste(sep = "", "tkp.", tkp.id, "$params$", what, "<-tmp")
  eval(parse(text = cmd), .tkplot.env)
  rm("tmp", envir = .tkplot.env)
  TRUE
  # nocov end
}

.tkplot.set.vertex.coords <- function(tkp.id, id, x, y) {
  # nocov start
  cmd <- paste(
    sep = "",
    "tkp.",
    tkp.id,
    "$coords[",
    id,
    ",]<-c(",
    x,
    ",",
    y,
    ")"
  )
  eval(parse(text = cmd), .tkplot.env)
  TRUE
  # nocov end
}

.tkplot.set.label.degree <- function(tkp.id, id, phi) {
  # nocov start
  tkp <- .tkplot.get(tkp.id)

  if (length(tkp$params$label.degree) == 1) {
    label.degree <- rep(tkp$params$label.degree, times = vcount(tkp$graph))
    label.degree[id] <- phi
    assign("tmp", label.degree, .tkplot.env)
    cmd <- paste(sep = "", "tkp.", tkp.id, "$params$label.degree <- tmp")
    eval(parse(text = cmd), .tkplot.env)
    rm("tmp", envir = .tkplot.env)
  } else {
    cmd <- paste(
      sep = "",
      "tkp.",
      tkp.id,
      "$params$label.degree[",
      id,
      "] <- ",
      phi
    )
    eval(parse(text = cmd), .tkplot.env)
  }
  TRUE
  # nocov end
}

###################################################################
# Internal functions, creating and updating canvas objects
###################################################################
# nocov start
# Creates a new vertex tk object
.tkplot.create.vertex <- function(tkp.id, id, label, x = 0, y = 0) {
  tkp <- .tkplot.get(tkp.id)
  vertex.size <- tkp$params$vertex.params[id, "vertex.size"]
  vertex.color <- tkp$params$vertex.params[id, "vertex.color"]
  vertex.frame.color <- ifelse(
    length(tkp$params$vertex.frame.color) > 1,
    tkp$params$vertex.frame.color[id],
    tkp$params$vertex.frame.color
  )
  item <- tcltk::tkcreate(
    tkp$canvas,
    "oval",
    x - vertex.size,
    y - vertex.size,
    x + vertex.size,
    y + vertex.size,
    width = 1,
    outline = vertex.frame.color,
    fill = vertex.color
  )
  tcltk::tkaddtag(tkp$canvas, "vertex", "withtag", item)
  tcltk::tkaddtag(tkp$canvas, paste("v-", id, sep = ""), "withtag", item)
  if (!is.na(label)) {
    label.degree <- ifelse(
      length(tkp$params$label.degree) > 1,
      tkp$params$label.degree[id],
      tkp$params$label.degree
    )
    label.color <- if (length(tkp$params$label.color) > 1) {
      tkp$params$label.color[id]
    } else {
      tkp$params$label.color
    }
    label.dist <- tkp$params$label.dist
    label.x <- x +
      label.dist *
        cos(label.degree) *
        (vertex.size + 6 + 4 * (ceiling(log10(id))))
    label.y <- y +
      label.dist *
        sin(label.degree) *
        (vertex.size + 6 + 4 * (ceiling(log10(id))))
    if (label.dist == 0) {
      afill <- label.color
    } else {
      afill <- "red"
    }
    litem <- tcltk::tkcreate(
      tkp$canvas,
      "text",
      label.x,
      label.y,
      text = as.character(label),
      state = "normal",
      fill = label.color,
      activefill = afill,
      font = tkp$params$vertex.params[id, "label.font"]
    )
    tcltk::tkaddtag(tkp$canvas, "label", "withtag", litem)
    tcltk::tkaddtag(tkp$canvas, paste("v-", id, sep = ""), "withtag", litem)
  }
  item
}

# Create all vertex objects and move them into correct position
.tkplot.create.vertices <- function(tkp.id) {
  tkp <- .tkplot.get(tkp.id)
  n <- vcount(tkp$graph)

  # Labels
  labels <- i.get.labels(tkp$graph, tkp$labels)

  mapply(
    function(v, l, x, y) .tkplot.create.vertex(tkp.id, v, l, x, y),
    1:n,
    labels,
    tkp$coords[, 1],
    tkp$coords[, 2]
  )
}

.tkplot.update.label <- function(tkp.id, id, x, y) {
  tkp <- .tkplot.get(tkp.id)
  vertex.size <- tkp$params$vertex.params[id, "vertex.size"]
  label.degree <- ifelse(
    length(tkp$params$label.degree) > 1,
    tkp$params$label.degree[id],
    tkp$params$label.degree
  )
  label.dist <- tkp$params$label.dist
  label.x <- x +
    label.dist *
      cos(label.degree) *
      (vertex.size + 6 + 4 * (ceiling(log10(id))))
  label.y <- y +
    label.dist *
      sin(label.degree) *
      (vertex.size + 6 + 4 * (ceiling(log10(id))))
  tcltk::tkcoords(
    tkp$canvas,
    paste("label&&v-", id, sep = ""),
    label.x,
    label.y
  )
}

.tkplot.update.vertex <- function(tkp.id, id, x, y) {
  tkp <- .tkplot.get(tkp.id)
  vertex.size <- tkp$params$vertex.params[id, "vertex.size"]
  # Vertex
  tcltk::tkcoords(
    tkp$canvas,
    paste("vertex&&v-", id, sep = ""),
    x - vertex.size,
    y - vertex.size,
    x + vertex.size,
    y + vertex.size
  )
  # Label
  .tkplot.update.label(tkp.id, id, x, y)

  # Edges
  edge.from.ids <- as.numeric(tcltk::tkfind(
    tkp$canvas,
    "withtag",
    paste("from-", id, sep = "")
  ))
  edge.to.ids <- as.numeric(tcltk::tkfind(
    tkp$canvas,
    "withtag",
    paste("to-", id, sep = "")
  ))
  for (i in seq(along.with = edge.from.ids)) {
    .tkplot.update.edge(tkp.id, edge.from.ids[i])
  }
  for (i in seq(along.with = edge.to.ids)) {
    .tkplot.update.edge(tkp.id, edge.to.ids[i])
  }
}

.tkplot.update.vertices <- function(tkp.id) {
  tkp <- .tkplot.get(tkp.id)
  n <- vcount(tkp$graph)
  mapply(
    function(v, x, y) .tkplot.update.vertex(tkp.id, v, x, y),
    1:n,
    tkp$coords[, 1],
    tkp$coords[, 2]
  )
}

# Creates tk object for edge 'id'
.tkplot.create.edge <- function(tkp.id, from, to, id) {
  tkp <- .tkplot.get(tkp.id)
  from.c <- tkp$coords[from, ]
  to.c <- tkp$coords[to, ]
  edge.color <- ifelse(
    length(tkp$params$edge.color) > 1,
    tkp$params$edge.color[id],
    tkp$params$edge.color
  )
  edge.width <- ifelse(
    length(tkp$params$edge.width) > 1,
    tkp$params$edge.width[id],
    tkp$params$edge.width
  )
  edge.lty <- ifelse(
    length(tkp$params$edge.lty) > 1,
    tkp$params$edge.lty[[id]],
    tkp$params$edge.lty
  )
  arrow.mode <- ifelse(
    length(tkp$params$arrow.mode) > 1,
    tkp$params$arrow.mode[[id]],
    tkp$params$arrow.mode
  )
  arrow.size <- tkp$params$arrow.size
  curved <- tkp$params$curved[[id]]
  arrow <- c("none", "first", "last", "both")[arrow.mode + 1]

  if (from != to) {
    ## non-loop edge
    if (is.logical(curved)) {
      curved <- curved * 0.5
    }
    if (curved != 0) {
      smooth <- TRUE
      midx <- (from.c[1] + to.c[1]) / 2
      midy <- (from.c[2] + to.c[2]) / 2
      spx <- midx - curved * 1 / 2 * (from.c[2] - to.c[2])
      spy <- midy + curved * 1 / 2 * (from.c[1] - to.c[1])
      coords <- c(from.c[1], from.c[2], spx, spy, to.c[1], to.c[2])
    } else {
      smooth <- FALSE
      coords <- c(from.c[1], from.c[2], to.c[1], to.c[2])
    }
    args <- c(
      list(tkp$canvas, "line"),
      coords,
      list(
        width = edge.width,
        activewidth = 2 * edge.width,
        arrow = arrow,
        arrowshape = arrow.size * c(10, 10, 5),
        fill = edge.color,
        activefill = "red",
        dash = edge.lty,
        tags = c(
          "edge",
          paste(sep = "", "edge-", id),
          paste(sep = "", "from-", from),
          paste(sep = "", "to-", to)
        )
      ),
      smooth = smooth
    )
    do.call(tcltk::tkcreate, args)
  } else {
    ## loop edge
    ## the coordinates are not correct but we will call update anyway...
    tcltk::tkcreate(
      tkp$canvas,
      "line",
      from.c[1],
      from.c[2],
      from.c[1] + 20,
      from.c[1] - 10,
      from.c[2] + 30,
      from.c[2],
      from.c[1] + 20,
      from.c[1] + 10,
      from.c[1],
      from.c[2],
      width = edge.width,
      activewidth = 2 * edge.width,
      arrow = arrow,
      arrowshape = arrow.size * c(10, 10, 5),
      dash = edge.lty,
      fill = edge.color,
      activefill = "red",
      smooth = TRUE,
      tags = c(
        "edge",
        "loop",
        paste(sep = "", "edge-", id),
        paste(sep = "", "from-", from),
        paste(sep = "", "to-", to)
      )
    )
  }

  edge.label <- ifelse(
    length(tkp$params$edge.labels) > 1,
    tkp$params$edge.labels[id],
    tkp$params$edge.labels
  )
  if (!is.na(edge.label)) {
    label.color <- ifelse(
      length(tkp$params$edge.label.color) > 1,
      tkp$params$edge.label.color[id],
      tkp$params$edge.label.color
    )
    ## not correct for loop edges but we will update anyway...
    label.x <- (to.c[1] + from.c[1]) / 2
    label.y <- (to.c[2] + from.c[2]) / 2
    litem <- tcltk::tkcreate(
      tkp$canvas,
      "text",
      label.x,
      label.y,
      text = as.character(edge.label),
      state = "normal",
      fill = label.color,
      font = tkp$params$edge.label.font
    )
    tcltk::tkaddtag(tkp$canvas, "label", "withtag", litem)
    tcltk::tkaddtag(tkp$canvas, paste(sep = "", "edge-", id), "withtag", litem)
  }
}

# Creates all edges
.tkplot.create.edges <- function(tkp.id) {
  tkp <- .tkplot.get(tkp.id)
  n <- ecount(tkp$graph)
  edgematrix <- as_edgelist(tkp$graph, names = FALSE)
  mapply(
    function(from, to, id) .tkplot.create.edge(tkp.id, from, to, id),
    edgematrix[, 1],
    edgematrix[, 2],
    1:nrow(edgematrix)
  )
}

# Update an edge with given itemid (not edge id!)
.tkplot.update.edge <- function(tkp.id, itemid) {
  tkp <- .tkplot.get(tkp.id)
  tags <- as.character(tcltk::tkgettags(tkp$canvas, itemid))
  from <- as.numeric(substring(
    grep("from-", tags, value = TRUE, fixed = TRUE),
    6
  ))
  to <- as.numeric(substring(grep("to-", tags, value = TRUE, fixed = TRUE), 4))
  from.c <- tkp$coords[from, ]
  to.c <- tkp$coords[to, ]

  edgeid <- as.numeric(substring(tags[pmatch("edge-", tags)], 6))

  if (from != to) {
    phi <- atan2(to.c[2] - from.c[2], to.c[1] - from.c[1])
    r <- sqrt((to.c[1] - from.c[1])^2 + (to.c[2] - from.c[2])^2)
    vertex.size <- tkp$params$vertex.params[to, "vertex.size"]
    vertex.size2 <- tkp$params$vertex.params[from, "vertex.size"]
    curved <- tkp$params$curved[[edgeid]]
    to.c[1] <- from.c[1] + (r - vertex.size) * cos(phi)
    to.c[2] <- from.c[2] + (r - vertex.size) * sin(phi)
    from.c[1] <- from.c[1] + vertex.size2 * cos(phi)
    from.c[2] <- from.c[2] + vertex.size2 * sin(phi)
    if (is.logical(curved)) {
      curved <- curved * 0.5
    }
    if (curved == 0) {
      tcltk::tkcoords(
        tkp$canvas,
        itemid,
        from.c[1],
        from.c[2],
        to.c[1],
        to.c[2]
      )
    } else {
      midx <- (from.c[1] + to.c[1]) / 2
      midy <- (from.c[2] + to.c[2]) / 2
      spx <- midx - curved * 1 / 2 * (from.c[2] - to.c[2])
      spy <- midy + curved * 1 / 2 * (from.c[1] - to.c[1])
      tcltk::tkcoords(
        tkp$canvas,
        itemid,
        from.c[1],
        from.c[2],
        spx,
        spy,
        to.c[1],
        to.c[2]
      )
    }
  } else {
    vertex.size <- tkp$params$vertex.params[to, "vertex.size"]
    loop.angle <- ifelse(
      length(tkp$param$loop.angle) > 1,
      tkp$params$loop.angle[edgeid],
      tkp$params$loop.angle
    )
    xx <- from.c[1] + cos(loop.angle / 180 * pi) * vertex.size
    yy <- from.c[2] + sin(loop.angle / 180 * pi) * vertex.size
    cc <- matrix(
      c(xx, yy, xx + 20, yy - 10, xx + 30, yy, xx + 20, yy + 10, xx, yy),
      ncol = 2,
      byrow = TRUE
    )

    phi <- atan2(cc[, 2] - yy, cc[, 1] - xx)
    r <- sqrt((cc[, 1] - xx)**2 + (cc[, 2] - yy)**2)
    phi <- phi + loop.angle / 180 * pi
    cc[, 1] <- xx + r * cos(phi)
    cc[, 2] <- yy + r * sin(phi)
    tcltk::tkcoords(
      tkp$canvas,
      itemid,
      cc[1, 1],
      cc[1, 2],
      cc[2, 1],
      cc[2, 2],
      cc[3, 1],
      cc[3, 2],
      cc[4, 1],
      cc[4, 2],
      cc[5, 1] + 0.001,
      cc[5, 2] + 0.001
    )
  }

  edge.label <- ifelse(
    length(tkp$params$edge.labels) > 1,
    tkp$params$edge.labels[edgeid],
    tkp$params$edge.labels
  )
  if (!is.na(edge.label)) {
    if (from != to) {
      label.x <- (to.c[1] + from.c[1]) / 2
      label.y <- (to.c[2] + from.c[2]) / 2
    } else {
      ## loops
      label.x <- xx + cos(loop.angle / 180 * pi) * 30
      label.y <- yy + sin(loop.angle / 180 * pi) * 30
    }
    litem <- as.numeric(tcltk::tkfind(
      tkp$canvas,
      "withtag",
      paste(sep = "", "label&&edge-", edgeid)
    ))
    tcltk::tkcoords(tkp$canvas, litem, label.x, label.y)
  }
}

.tkplot.toggle.labels <- function(tkp.id) {
  .tkplot.set.params(
    tkp.id,
    "labels.state",
    1 - .tkplot.get(tkp.id, "params")$labels.state
  )
  tkp <- .tkplot.get(tkp.id)
  state <- ifelse(tkp$params$labels.state == 1, "normal", "hidden")
  tcltk::tkitemconfigure(tkp$canvas, "label", "-state", state)
}

.tkplot.toggle.grid <- function(tkp.id) {
  .tkplot.set.params(
    tkp.id,
    "grid",
    1 - .tkplot.get(tkp.id, "params")$grid
  )
  tkp <- .tkplot.get(tkp.id)
  state <- ifelse(tkp$params$grid == 1, "normal", "hidden")
  if (state == "hidden") {
    tcltk::tkdelete(tkp$canvas, "grid")
  } else {
    tcltk::tkcreate(tkp$canvas, "grid", 0, 0, 10, 10, tags = c("grid"))
  }
}

.tkplot.update.vertex.color <- function(tkp.id, vids, newcolor) {
  tkp <- .tkplot.get(tkp.id)
  vparams <- tkp$params$vertex.params
  vparams[vids, "vertex.color"] <- newcolor
  .tkplot.set(tkp.id, "params$vertex.params", vparams)
  tcltk::tkitemconfigure(tkp$canvas, "selected&&vertex", "-fill", newcolor)
}

.tkplot.update.edge.color <- function(tkp.id, eids, newcolor) {
  tkp <- .tkplot.get(tkp.id)
  colors <- tkp$params$edge.color
  if (length(colors) == 1 && length(eids) == ecount(tkp$graph)) {
    ## Uniform color -> uniform color
    .tkplot.set(tkp.id, "params$edge.color", newcolor)
  } else if (length(colors) == 1) {
    ## Uniform color -> nonuniform color
    colors <- rep(colors, ecount(tkp$graph))
    colors[eids] <- newcolor
    .tkplot.set(tkp.id, "params$edge.color", colors)
  } else if (length(eids) == ecount(tkp$graph)) {
    ## Non-uniform -> uniform
    .tkplot.set(tkp.id, "params$edge.color", newcolor)
  } else {
    ## Non-uniform -> non-uniform
    colors[eids] <- newcolor
    .tkplot.set(tkp.id, "params$edge.color", colors)
  }

  tcltk::tkitemconfigure(tkp$canvas, "selected&&edge", "-fill", newcolor)
}

.tkplot.update.edge.width <- function(tkp.id, eids, newwidth) {
  tkp <- .tkplot.get(tkp.id)
  widths <- tkp$params$edge.width
  if (length(widths) == 1 && length(eids) == ecount(tkp$graph)) {
    ## Uniform width -> uniform width
    .tkplot.set(tkp.id, "params$edge.width", newwidth)
  } else if (length(widths) == 1) {
    ## Uniform width -> nonuniform width
    widths <- rep(widths, ecount(tkp$graph))
    widths[eids] <- newwidth
    .tkplot.set(tkp.id, "params$edge.width", widths)
  } else if (length(eids) == ecount(tkp$graph)) {
    ## Non-uniform -> uniform
    .tkplot.set(tkp.id, "params$edge.width", newwidth)
  } else {
    ## Non-uniform -> non-uniform
    widths[eids] <- newwidth
    .tkplot.set(tkp.id, "params$edge.width", widths)
  }

  tcltk::tkitemconfigure(tkp$canvas, "selected&&edge", "-width", newwidth)
}


.tkplot.update.vertex.size <- function(tkp.id, vids, newsize) {
  tkp <- .tkplot.get(tkp.id)
  vparams <- tkp$params$vertex.params
  vparams[vids, "vertex.size"] <- newsize
  .tkplot.set(tkp.id, "params$vertex.params", vparams)
  sapply(vids, function(id) {
    .tkplot.update.vertex(tkp.id, id, tkp$coords[id, 1], tkp$coords[id, 2])
  })
}

.tkplot.get.numeric.vector <- function(...) {
  labels <- list(...)
  if (length(labels) == 0) {
    return(FALSE)
  }

  answers <- as.list(rep("", length(labels)))
  dialog <- tcltk::tktoplevel()
  vars <- lapply(answers, tcltk::tclVar)

  retval <- list()

  OnOK <- function() {
    retval <<- lapply(vars, tcltk::tclvalue)
    tcltk::tkdestroy(dialog)
  }

  OK.but <- tcltk::tkbutton(dialog, text = "   OK   ", command = OnOK)
  for (i in seq(along.with = labels)) {
    tcltk::tkgrid(tcltk::tklabel(dialog, text = labels[[i]]))
    tmp <- tcltk::tkentry(dialog, width = "40", textvariable = vars[[i]])
    tcltk::tkgrid(tmp)
    tcltk::tkbind(tmp, "<Return>", OnOK)
  }
  tcltk::tkgrid(OK.but)
  tcltk::tkwait.window(dialog)

  retval <- lapply(retval, function(v) {
    eval(parse(text = paste("c(", v, ")")))
  })
  return(retval)
}

.tkplot.select.number <- function(label, initial, low = 1, high = 100) {
  dialog <- tcltk::tktoplevel()
  SliderValue <- tcltk::tclVar(as.character(initial))
  SliderValueLabel <- tcltk::tklabel(
    dialog,
    text = as.character(tcltk::tclvalue(SliderValue))
  )
  tcltk::tkgrid(tcltk::tklabel(dialog, text = label), SliderValueLabel)
  tcltk::tkconfigure(SliderValueLabel, textvariable = SliderValue)
  slider <- tcltk::tkscale(
    dialog,
    from = high,
    to = low,
    showvalue = F,
    variable = SliderValue,
    resolution = 1,
    orient = "horizontal"
  )
  OnOK <- function() {
    SliderValue <<- as.numeric(tcltk::tclvalue(SliderValue))
    tcltk::tkdestroy(dialog)
  }
  OnCancel <- function() {
    SliderValue <<- NA
    tcltk::tkdestroy(dialog)
  }
  OK.but <- tcltk::tkbutton(dialog, text = "   OK   ", command = OnOK)
  cancel.but <- tcltk::tkbutton(dialog, text = " Cancel ", command = OnCancel)
  tcltk::tkgrid(slider)
  tcltk::tkgrid(OK.but, cancel.but)

  tcltk::tkwait.window(dialog)
  return(SliderValue)
}

###################################################################
# Internal functions, vertex and edge selection
###################################################################

.tkplot.deselect.all <- function(tkp.id) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  ids <- as.numeric(tcltk::tkfind(canvas, "withtag", "selected"))
  for (i in ids) {
    .tkplot.deselect.this(tkp.id, i)
  }
}

.tkplot.select.all.vertices <- function(tkp.id) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  vertices <- as.numeric(tcltk::tkfind(canvas, "withtag", "vertex"))
  for (i in vertices) {
    .tkplot.select.vertex(tkp.id, i)
  }
}

.tkplot.select.some.vertices <- function(tkp.id, vids) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  vids <- unique(vids)
  for (i in vids) {
    tkid <- as.numeric(tcltk::tkfind(
      canvas,
      "withtag",
      paste(sep = "", "vertex&&v-", i)
    ))
    .tkplot.select.vertex(tkp.id, tkid)
  }
}

.tkplot.select.all.edges <- function(tkp.id, vids) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  edges <- as.numeric(tcltk::tkfind(canvas, "withtag", "edge"))
  for (i in edges) {
    .tkplot.select.edge(tkp.id, i)
  }
}

.tkplot.select.some.edges <- function(tkp.id, from, to) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  fromtags <- sapply(from, function(i) {
    paste(sep = "", "from-", i)
  })
  totags <- sapply(from, function(i) {
    paste(sep = "", "to-", i)
  })
  edges <- as.numeric(tcltk::tkfind(canvas, "withtag", "edge"))
  for (i in edges) {
    tags <- as.character(tcltk::tkgettags(canvas, i))
    ftag <- tags[pmatch("from-", tags)]
    ttag <- tags[pmatch("to-", tags)]
    if (ftag %in% fromtags && ttag %in% totags) {
      .tkplot.select.edge(tkp.id, i)
    }
  }
}

.tkplot.select.vertex <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tcltk::tkaddtag(canvas, "selected", "withtag", tkid)
  tcltk::tkitemconfigure(
    canvas,
    tkid,
    "-outline",
    "red",
    "-width",
    2
  )
}

.tkplot.select.edge <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tcltk::tkaddtag(canvas, "selected", "withtag", tkid)
  tcltk::tkitemconfigure(canvas, tkid, "-dash", "-")
}

.tkplot.select.label <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tcltk::tkaddtag(canvas, "selected", "withtag", tkid)
}

.tkplot.deselect.vertex <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tcltk::tkdtag(canvas, tkid, "selected")
  tkp <- .tkplot.get(tkp.id)
  tags <- as.character(tcltk::tkgettags(canvas, tkid))
  id <- as.numeric(substring(tags[pmatch("v-", tags)], 3))
  vertex.frame.color <- ifelse(
    length(tkp$params$vertex.frame.color) > 1,
    tkp$params$vertex.frame.color[id],
    tkp$params$vertex.frame.color
  )
  tcltk::tkitemconfigure(
    canvas,
    tkid,
    "-outline",
    vertex.frame.color,
    "-width",
    1
  )
}

.tkplot.deselect.edge <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tcltk::tkdtag(canvas, tkid, "selected")
  tkp <- .tkplot.get(tkp.id)
  tags <- as.character(tcltk::tkgettags(canvas, tkid))
  id <- as.numeric(substring(tags[pmatch("edge-", tags)], 6))
  edge.lty <- ifelse(
    length(tkp$params$edge.lty) > 1,
    tkp$params$edge.lty[[id]],
    tkp$params$edge.lty
  )
  tcltk::tkitemconfigure(canvas, tkid, "-dash", edge.lty)
}

.tkplot.deselect.label <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tcltk::tkdtag(canvas, tkid, "selected")
}

.tkplot.select.current <- function(tkp.id) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tkid <- as.numeric(tcltk::tkfind(canvas, "withtag", "current"))
  .tkplot.select.this(tkp.id, tkid)
}

.tkplot.deselect.current <- function(tkp.id) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tkid <- as.numeric(tcltk::tkfind(canvas, "withtag", "current"))
  .tkplot.deselect.this(tkp.id, tkid)
}

.tkplot.select.this <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tags <- as.character(tcltk::tkgettags(canvas, tkid))
  if ("vertex" %in% tags) {
    .tkplot.select.vertex(tkp.id, tkid)
  } else if ("edge" %in% tags) {
    .tkplot.select.edge(tkp.id, tkid)
  } else if ("label" %in% tags) {
    tkp <- .tkplot.get(tkp.id)
    if (tkp$params$label.dist == 0) {
      id <- tags[pmatch("v-", tags)]
      tkid <- as.character(tcltk::tkfind(
        canvas,
        "withtag",
        paste(sep = "", "vertex&&", id)
      ))
      .tkplot.select.vertex(tkp.id, tkid)
    } else {
      .tkplot.select.label(tkp.id, tkid)
    }
  }
}

.tkplot.deselect.this <- function(tkp.id, tkid) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tags <- as.character(tcltk::tkgettags(canvas, tkid))
  if ("vertex" %in% tags) {
    .tkplot.deselect.vertex(tkp.id, tkid)
  } else if ("edge" %in% tags) {
    .tkplot.deselect.edge(tkp.id, tkid)
  } else if ("label" %in% tags) {
    tkp <- .tkplot.get(tkp.id)
    if (tkp$params$label.dist == 0) {
      id <- tags[pmatch("v-", tags)]
      tkid <- as.character(tcltk::tkfind(
        canvas,
        "withtag",
        paste(sep = "", "vertex&&", id)
      ))
      .tkplot.deselect.vertex(tkp.id, tkid)
    } else {
      .tkplot.deselect.label(tkp.id, tkid)
    }
  }
}

.tkplot.get.selected.vertices <- function(tkp.id) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tkids <- as.numeric(tcltk::tkfind(canvas, "withtag", "vertex&&selected"))

  ids <- sapply(tkids, function(tkid) {
    tags <- as.character(tcltk::tkgettags(canvas, tkid))
    id <- as.numeric(substring(tags[pmatch("v-", tags)], 3))
    id
  })

  ids
}

.tkplot.get.selected.edges <- function(tkp.id) {
  canvas <- .tkplot.get(tkp.id, "canvas")
  tkids <- as.numeric(tcltk::tkfind(canvas, "withtag", "edge&&selected"))

  ids <- sapply(tkids, function(tkid) {
    tags <- as.character(tcltk::tkgettags(canvas, tkid))
    id <- as.numeric(substring(tags[pmatch("edge-", tags)], 6))
    id
  })

  ids
}

###################################################################
# Internal functions: manipulating the UI
###################################################################

.tkplot.select.menu <- function(tkp.id, main.menu) {
  select.menu <- tcltk::tkmenu(main.menu)

  tcltk::tkadd(
    select.menu,
    "command",
    label = "Select all vertices",
    command = function() {
      .tkplot.deselect.all(tkp.id)
      .tkplot.select.all.vertices(tkp.id)
    }
  )
  tcltk::tkadd(
    select.menu,
    "command",
    label = "Select all edges",
    command = function() {
      .tkplot.deselect.all(tkp.id)
      .tkplot.select.all.edges(tkp.id)
    }
  )
  tcltk::tkadd(
    select.menu,
    "command",
    label = "Select some vertices...",
    command = function() {
      vids <- .tkplot.get.numeric.vector("Select vertices")
      .tkplot.select.some.vertices(tkp.id, vids[[1]])
    }
  )
  tcltk::tkadd(
    select.menu,
    "command",
    label = "Select some edges...",
    command = function() {
      fromto <- .tkplot.get.numeric.vector(
        "Select edges from vertices",
        "to vertices"
      )
      .tkplot.select.some.edges(tkp.id, fromto[[1]], fromto[[2]])
    }
  )
  tcltk::tkadd(select.menu, "separator")
  tcltk::tkadd(
    select.menu,
    "command",
    label = "Deselect everything",
    command = function() {
      .tkplot.deselect.all(tkp.id)
    }
  )

  select.menu
}

.tkplot.layout.menu <- function(tkp.id, main.menu) {
  layout.menu <- tcltk::tkmenu(main.menu)

  sapply(.tkplot.getlayoutlist(), function(n) {
    tcltk::tkadd(
      layout.menu,
      "command",
      label = .tkplot.getlayoutname(n),
      command = function() {
        .tkplot.layout.dialog(tkp.id, n)
      }
    )
  })

  layout.menu
}

.tkplot.layout.dialog <- function(tkp.id, layout.name) {
  layout <- .tkplot.getlayout(layout.name)

  # No parameters
  if (length(layout$params) == 0) {
    return(tk_reshape(tkp.id, layout$f, params = list()))
  }

  submit <- function() {
    realparams <- params <- vector(mode = "list", length(layout$params))
    names(realparams) <- names(params) <- names(layout$params)
    for (i in seq(along.with = layout$params)) {
      realparams[[i]] <-
        params[[i]] <- switch(
          layout$params[[i]]$type,
          "numeric" = as.numeric(tcltk::tkget(values[[i]])),
          "character" = as.character(tcltk::tkget(values[[i]])),
          "logical" = as.logical(tcltk::tclvalue(values[[i]])),
          "choice" = as.character(tcltk::tclvalue(values[[i]])),
          "initial" = as.logical(tcltk::tclvalue(values[[i]])),
          "expression" = as.numeric(tcltk::tkget(values[[i]]))
        )
      if (
        layout$params[[i]]$type == "initial" &&
          params[[i]]
      ) {
        realparams[[i]] <- tk_coords(tkp.id, norm = TRUE)
      }
    }
    if (as.logical(tcltk::tclvalue(save.default))) {
      .tkplot.layouts.newdefaults(layout.name, params)
    }
    tcltk::tkdestroy(dialog)
    tk_reshape(tkp.id, layout$f, params = realparams)
  }

  dialog <- tcltk::tktoplevel(.tkplot.get(tkp.id, "top"))

  tcltk::tkwm.title(dialog, paste("Layout parameters for graph plot", tkp.id))
  tcltk::tkwm.transient(dialog, .tkplot.get(tkp.id, "top"))

  tcltk::tkgrid(
    tcltk::tklabel(
      dialog,
      text = paste(layout$name, "layout"),
      font = tcltk::tkfont.create(
        family = "helvetica",
        size = 20,
        weight = "bold"
      )
    ),
    row = 0,
    column = 0,
    columnspan = 2,
    padx = 10,
    pady = 10
  )

  row <- 1
  values <- list()
  for (i in seq(along.with = layout$params)) {
    tcltk::tkgrid(
      tcltk::tklabel(
        dialog,
        text = paste(sep = "", layout$params[[i]]$name, ":")
      ),
      row = row,
      column = 0,
      sticky = "ne",
      padx = 5,
      pady = 5
    )

    if (layout$params[[i]]$type %in% c("numeric", "character")) {
      values[[i]] <- tcltk::tkentry(dialog)
      tcltk::tkinsert(values[[i]], 0, as.character(layout$params[[i]]$default))
      tcltk::tkgrid(
        values[[i]],
        row = row,
        column = 1,
        sticky = "nw",
        padx = 5,
        pady = 5
      )
    } else if (layout$params[[i]]$type == "logical") {
      values[[i]] <- tcltk::tclVar(as.character(layout$params[[i]]$default))
      tmp <- tcltk::tkcheckbutton(
        dialog,
        onvalue = "TRUE",
        offvalue = "FALSE",
        variable = values[[i]]
      )
      tcltk::tkgrid(
        tmp,
        row = row,
        column = 1,
        sticky = "nw",
        padx = 5,
        pady = 5
      )
    } else if (layout$params[[i]]$type == "choice") {
      tmp.frame <- tcltk::tkframe(dialog)
      tcltk::tkgrid(
        tmp.frame,
        row = row,
        column = 1,
        sticky = "nw",
        padx = 5,
        pady = 5
      )
      values[[i]] <- tcltk::tclVar(layout$params[[i]]$default)
      for (j in 1:length(layout$params[[i]]$values)) {
        tmp <- tcltk::tkradiobutton(
          tmp.frame,
          variable = values[[i]],
          value = layout$params[[i]]$values[j],
          text = layout$params[[i]]$values[j]
        )
        tcltk::tkpack(tmp, anchor = "nw")
      }
    } else if (layout$params[[i]]$type == "initial") {
      values[[i]] <- tcltk::tclVar(as.character(layout$params[[i]]$default))
      tcltk::tkgrid(
        tcltk::tkcheckbutton(
          dialog,
          onvalue = "TRUE",
          offvalue = "FALSE",
          variable = values[[i]]
        ),
        row = row,
        column = 1,
        sticky = "nw",
        padx = 5,
        pady = 5
      )
    } else if (layout$param[[i]]$type == "expression") {
      values[[i]] <- tcltk::tkentry(dialog)
      .tkplot.g <- .tkplot.get(tkp.id, "graph")
      tcltk::tkinsert(
        values[[i]],
        0,
        as.character(eval(layout$params[[i]]$default))
      )
      tcltk::tkgrid(
        values[[i]],
        row = row,
        column = 1,
        sticky = "nw",
        padx = 5,
        pady = 5
      )
    }

    row <- row + 1
  } # for along layout$params

  tcltk::tkgrid(
    tcltk::tklabel(dialog, text = "Set these as defaults"),
    sticky = "ne",
    row = row,
    column = 0,
    padx = 5,
    pady = 5
  )
  save.default <- tcltk::tclVar("FALSE")
  tcltk::tkgrid(
    tcltk::tkcheckbutton(
      dialog,
      onvalue = "TRUE",
      offvalue = "FALSE",
      variable = save.default,
      text = ""
    ),
    row = row,
    column = 1,
    sticky = "nw",
    padx = 5,
    pady = 5
  )
  row <- row + 1

  tcltk::tkgrid(
    tcltk::tkbutton(dialog, text = "OK", command = submit),
    row = row,
    column = 0
  )
  tcltk::tkgrid(
    tcltk::tkbutton(dialog, text = "Cancel", command = function() {
      tcltk::tkdestroy(dialog)
      invisible(TRUE)
    }),
    row = row,
    column = 1
  )
}

.tkplot.select.color <- function(initialcolor) {
  color <- tcltk::tclvalue(tcltk::tcl(
    "tk_chooseColor",
    initialcolor = initialcolor,
    title = "Choose a color"
  ))
  return(color)
}

###################################################################
# Internal functions: other
###################################################################

#' @importFrom grDevices palette
.tkplot.convert.color <- function(col) {
  if (is.numeric(col)) {
    ## convert numeric color based on current palette
    p <- palette()
    col <- col %% length(p)
    col[col == 0] <- length(p)
    col <- palette()[col]
  } else if (
    is.character(col) && any(substr(col, 1, 1) == "#" & nchar(col) == 9)
  ) {
    ## drop alpha channel, tcltk doesn't support it
    idx <- substr(col, 1, 1) == "#" & nchar(col) == 9
    col[idx] <- substr(col[idx], 1, 7)
  }

  ## replace NA's with ""
  col[is.na(col)] <- ""

  col
}

.tkplot.convert.font <- function(font, family, cex) {
  tk.fonts <- as.character(tcltk::tkfont.names())
  if (as.character(font) %in% tk.fonts) {
    ## already defined Tk font
    as.character(font)
  } else {
    ## we create a font from familiy, font & cex
    font <- as.numeric(font)
    family <- as.character(family)
    cex <- as.numeric(cex)

    ## multiple sizes
    if (length(cex) > 1) {
      return(sapply(cex, .tkplot.convert.font, font = font, family = family))
    }

    ## set slant & weight
    if (font == 2) {
      slant <- "roman"
      weight <- "bold"
    } else if (font == 3) {
      slant <- "italic"
      weight <- "normal"
    } else if (font == 4) {
      slant <- "italic"
      weight <- "bold"
    } else {
      slant <- "roman"
      weight <- "normal"
    }

    ## set tkfamily
    if (family == "symbol" || font == 5) {
      tkfamily <- "symbol"
    } else if (family == "serif") {
      tkfamily <- "Times"
    } else if (family == "sans") {
      tkfamily <- "Helvetica"
    } else if (family == "mono") {
      tkfamily <- "Courier"
    } else {
      ## pass the family and see what happens
      tkfamily <- family
    }

    newfont <- tcltk::tkfont.create(
      family = tkfamily,
      slant = slant,
      weight = weight,
      size = as.integer(12 * cex)
    )
    as.character(newfont)
  }
}

i.tkplot.get.edge.lty <- function(edge.lty) {
  if (is.numeric(edge.lty)) {
    lty <- c(" ", "", "-", ".", "-.", "--", "--.")
    edge.lty <- lty[edge.lty %% 7 + 1]
  } else if (is.character(edge.lty)) {
    wh <- edge.lty %in%
      c(
        "blank",
        "solid",
        "dashed",
        "dotted",
        "dotdash",
        "longdash",
        "twodash"
      )
    lty <- c(" ", "", "-", ".", "-.", "--", "--.")
    names(lty) <- c(
      "blank",
      "solid",
      "dashed",
      "dotted",
      "dotdash",
      "longdash",
      "twodash"
    )
    edge.lty[wh] <- lty[edge.lty[wh]]
  }
  edge.lty
}
# nocov end
