src/Tools/Graphview/visualizer.scala
author wenzelm
Thu, 01 Jan 2015 12:34:15 +0100
changeset 59220 261ec482cd40
parent 59202 711c2446dc9d
child 59228 56b34fc7a015
permissions -rw-r--r--
tuned signature;

/*  Title:      Tools/Graphview/visualizer.scala
    Author:     Markus Kaiser, TU Muenchen

Graph visualization parameters and interface state.
*/

package isabelle.graphview


import isabelle._

import java.awt.{Font, FontMetrics, Color, Shape, RenderingHints, Graphics2D}
import java.awt.image.BufferedImage
import javax.swing.JComponent


class Visualizer(val model: Model)
{
  visualizer =>


  /* font rendering information */

  val font_family: String = "IsabelleText"
  val font_size: Int = 14
  val font = new Font(font_family, Font.BOLD, font_size)

  val rendering_hints =
    new RenderingHints(
      RenderingHints.KEY_ANTIALIASING,
      RenderingHints.VALUE_ANTIALIAS_ON)

  val gfx = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics
  gfx.setFont(font)
  gfx.setRenderingHints(rendering_hints)

  val font_metrics: FontMetrics = gfx.getFontMetrics(font)

  val tooltip_font_size: Int = 10


  /* rendering parameters */

  val gap_x = 20
  val pad_x = 8
  val pad_y = 5

  var arrow_heads = false

  object Colors
  {
    private val filter_colors = List(
      new Color(0xD9, 0xF2, 0xE2), // blue
      new Color(0xFF, 0xE7, 0xD8), // orange
      new Color(0xFF, 0xFF, 0xE5), // yellow
      new Color(0xDE, 0xCE, 0xFF), // lilac
      new Color(0xCC, 0xEB, 0xFF), // turquoise
      new Color(0xFF, 0xE5, 0xE5), // red
      new Color(0xE5, 0xE5, 0xD9)  // green
    )

    private var curr : Int = -1
    def next(): Color =
    {
      curr = (curr + 1) % filter_colors.length
      filter_colors(curr)
    }
  }


  object Coordinates
  {
    private var layout = Layout_Pendulum.empty_layout

    def apply(k: String): (Double, Double) =
      layout.nodes.get(k) match {
        case Some(c) => c
        case None => (0, 0)
      }

    def apply(e: (String, String)): List[(Double, Double)] =
      layout.dummies.get(e) match {
        case Some(ds) => ds
        case None => Nil
      }

    def reposition(k: String, to: (Double, Double))
    {
      layout = layout.copy(nodes = layout.nodes + (k -> to))
    }

    def reposition(d: ((String, String), Int), to: (Double, Double))
    {
      val (e, index) = d
      layout.dummies.get(e) match {
        case None =>
        case Some(ds) =>
          layout = layout.copy(dummies =
            layout.dummies + (e ->
              (ds.zipWithIndex :\ List.empty[(Double, Double)]) {
                case ((t, i), n) => if (index == i) to :: n else t :: n
              }))
      }
    }

    def translate(k: String, by: (Double, Double))
    {
      val ((x, y), (dx, dy)) = (Coordinates(k), by)
      reposition(k, (x + dx, y + dy))
    }

    def translate(d: ((String, String), Int), by: (Double, Double))
    {
      val ((e, i),(dx, dy)) = (d, by)
      val (x, y) = apply(e)(i)
      reposition(d, (x + dx, y + dy))
    }

    def update_layout()
    {
      layout =
        if (model.current.is_empty) Layout_Pendulum.empty_layout
        else {
          val max_width =
            model.current.iterator.map({ case (_, (info, _)) =>
              font_metrics.stringWidth(info.name).toDouble }).max
          val box_distance = max_width + pad_x + gap_x
          def box_height(n: Int): Double =
            ((font_metrics.getAscent + font_metrics.getDescent + pad_y) * (5 max n)).toDouble
          Layout_Pendulum(model.current, box_distance, box_height)
        }
    }

    def bounds(): (Double, Double, Double, Double) =
      model.visible_nodes_iterator.toList match {
        case Nil => (0, 0, 0, 0)
        case nodes =>
          val X: (String => Double) = (n => apply(n)._1)
          val Y: (String => Double) = (n => apply(n)._2)

          (X(nodes.minBy(X)), Y(nodes.minBy(Y)),
           X(nodes.maxBy(X)), Y(nodes.maxBy(Y)))
      }
  }

  object Drawer
  {
    def apply(g: Graphics2D, n: Option[String])
    {
      n match {
        case None =>
        case Some(_) => Shapes.Growing_Node.paint(g, visualizer, n)
      }
    }

    def apply(g: Graphics2D, e: (String, String), head: Boolean, dummies: Boolean)
    {
      Shapes.Cardinal_Spline_Edge.paint(g, visualizer, e, head, dummies)
    }

    def paint_all_visible(g: Graphics2D, dummies: Boolean)
    {
      g.setFont(font)

      g.setRenderingHints(rendering_hints)

      model.visible_edges_iterator.foreach(e => {
          apply(g, e, arrow_heads, dummies)
        })

      model.visible_nodes_iterator.foreach(l => {
          apply(g, Some(l))
        })
    }

    def shape(g: Graphics2D, n: Option[String]): Shape =
      n match {
        case None => Shapes.Dummy.shape(g, visualizer, None)
        case Some(_) => Shapes.Growing_Node.shape(g, visualizer, n)
      }
  }

  object Selection
  {
    private var selected: List[String] = Nil

    def apply() = selected
    def apply(s: String) = selected.contains(s)

    def add(s: String) { selected = s :: selected }
    def set(ss: List[String]) { selected = ss }
    def clear() { selected = Nil }
  }

  sealed case class Node_Color(border: Color, background: Color, foreground: Color)

  def node_color(l: Option[String]): Node_Color =
    l match {
      case None => Node_Color(Color.GRAY, Color.WHITE, Color.BLACK)
      case Some(c) =>
        if (Selection(c)) Node_Color(Color.BLUE, Color.GREEN, Color.BLACK)
        else Node_Color(Color.BLACK, model.colors.getOrElse(c, Color.WHITE), Color.BLACK)
    }

  def edge_color(e: (String, String)): Color = Color.BLACK

  object Caption
  {
    def apply(key: String) = model.complete.get_node(key).name
  }
}