src/Tools/Graphview/graph_panel.scala
author wenzelm
Mon, 05 Jan 2015 14:17:17 +0100
changeset 59287 9d4728e00925
parent 59286 ac74eedb910a
child 59290 569a8109eeb2
permissions -rw-r--r--
avoid fractional font size;

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

Graphview Java2D drawing panel.
*/

package isabelle.graphview


import isabelle._

import java.awt.{Dimension, Graphics2D, Point}
import java.awt.geom.{AffineTransform, Point2D}
import java.awt.image.BufferedImage
import javax.swing.{JScrollPane, JComponent, SwingUtilities}

import scala.swing.{Panel, ScrollPane}
import scala.swing.event.{Event, Key, MousePressed, MouseDragged, MouseClicked, MouseEvent}


class Graph_Panel(val visualizer: Visualizer) extends ScrollPane
{
  panel =>

  tooltip = ""

  override lazy val peer: JScrollPane = new JScrollPane with SuperMixin {
    override def getToolTipText(event: java.awt.event.MouseEvent): String =
      find_visible_node(Transform.pane_to_graph_coordinates(event.getPoint)) match {
        case Some(node) =>
          visualizer.model.full_graph.get_node(node) match {
            case Nil => null
            case content => visualizer.make_tooltip(panel.peer, event.getX, event.getY, content)
          }
        case None => null
      }
  }

  focusable = true
  requestFocus()

  horizontalScrollBarPolicy = ScrollPane.BarPolicy.Always
  verticalScrollBarPolicy = ScrollPane.BarPolicy.Always

  peer.getVerticalScrollBar.setUnitIncrement(10)

  def find_visible_node(at: Point2D): Option[Graph_Display.Node] =
  {
    val m = visualizer.metrics()
    visualizer.model.make_visible_graph().keys_iterator
      .find(node => visualizer.Drawer.shape(m, node).contains(at))
  }

  def refresh()
  {
    if (paint_panel != null) {
      paint_panel.set_preferred_size()
      paint_panel.repaint()
    }
  }

  def fit_to_window() = {
    Transform.fit_to_window()
    refresh()
  }

  val zoom = new GUI.Zoom_Box { def changed = rescale(0.01 * factor) }

  def rescale(s: Double)
  {
    Transform.scale = s
    if (zoom != null) zoom.set_item((Transform.scale_discrete * 100).floor.toInt)
    refresh()
  }

  def apply_layout()
  {
    visualizer.Coordinates.update_layout()
    repaint()
  }

  private class Paint_Panel extends Panel
  {
    def set_preferred_size()
    {
      val box = visualizer.Coordinates.bounding_box()
      val s = Transform.scale_discrete

      preferredSize =
        new Dimension((box.width * s).ceil.toInt, (box.height * s).ceil.toInt)

      revalidate()
    }

    override def paint(gfx: Graphics2D)
    {
      super.paintComponent(gfx)
      gfx.setColor(visualizer.background_color)
      gfx.fillRect(0, 0, peer.getWidth, peer.getHeight)
      gfx.transform(Transform())

      visualizer.Drawer.paint_all_visible(gfx, true)
    }
  }
  private val paint_panel = new Paint_Panel
  contents = paint_panel

  listenTo(mouse.moves)
  listenTo(mouse.clicks)
  reactions += Mouse_Interaction.react
  reactions +=
  {
    case MousePressed(_, _, _, _, _) => repaint()
    case MouseDragged(_, _, _) => repaint()
    case MouseClicked(_, _, _, _, _) => repaint()
  }

  visualizer.model.Colors.events += { case _ => repaint() }
  visualizer.model.Mutators.events += { case _ => repaint() }

  apply_layout()
  rescale(1.0)

  private object Transform
  {
    private var _scale: Double = 1.0
    def scale: Double = _scale
    def scale_=(s: Double)
    {
      _scale = (s min 10.0) max 0.1
    }

    def scale_discrete: Double =
    {
      val font_height = GUI.line_metrics(visualizer.font()).getHeight.toInt
      (scale * font_height).floor / font_height
    }

    def apply() =
    {
      val box = visualizer.Coordinates.bounding_box()
      val at = AffineTransform.getScaleInstance(scale_discrete, scale_discrete)
      at.translate(- box.x, - box.y)
      at
    }

    def fit_to_window()
    {
      if (visualizer.model.make_visible_graph().is_empty)
        rescale(1.0)
      else {
        val box = visualizer.Coordinates.bounding_box()
        rescale((size.width / box.width) min (size.height / box.height))
      }
    }

    def pane_to_graph_coordinates(at: Point2D): Point2D =
    {
      val s = Transform.scale_discrete
      val p = Transform().inverseTransform(peer.getViewport.getViewPosition, null)

      p.setLocation(p.getX + at.getX / s, p.getY + at.getY / s)
      p
    }
  }

  object Mouse_Interaction
  {
    private var draginfo: (Point, List[Graph_Display.Node], List[(Graph_Display.Edge, Int)]) = null

    val react: PartialFunction[Event, Unit] =
    {
      case MousePressed(_, p, _, _, _) => pressed(p)
      case MouseDragged(_, to, _) =>
        drag(draginfo, to)
        val (_, p, d) = draginfo
        draginfo = (to, p, d)
      case e @ MouseClicked(_, p, m, n, _) => click(p, m, n, e)
    }

    def dummy(at: Point2D): Option[(Graph_Display.Edge, Int)] =
    {
      val m = visualizer.metrics()
      visualizer.model.make_visible_graph().edges_iterator.map(
        edge =>
          visualizer.Coordinates.get_dummies(edge).zipWithIndex.map((edge, _))).flatten.find(
            {
              case (_, (p, _)) =>
                visualizer.Drawer.shape(m, Graph_Display.Node.dummy).
                  contains(at.getX() - p.x, at.getY() - p.y)
            }) match {
              case None => None
              case Some((edge, (_, index))) => Some((edge, index))
            }
    }

    def pressed(at: Point)
    {
      val c = Transform.pane_to_graph_coordinates(at)
      val l =
        find_visible_node(c) match {
          case Some(node) =>
            if (visualizer.Selection.contains(node)) visualizer.Selection.get()
            else List(node)
          case None => Nil
        }
      val d =
        l match {
          case Nil =>
            dummy(c) match {
              case Some(d) => List(d)
              case None => Nil
            }
          case _ => Nil
        }
      draginfo = (at, l, d)
    }

    def click(at: Point, m: Key.Modifiers, clicks: Int, e: MouseEvent)
    {
      val c = Transform.pane_to_graph_coordinates(at)

      def left_click()
      {
        (find_visible_node(c), m) match {
          case (Some(node), Key.Modifier.Control) => visualizer.Selection.add(node)
          case (None, Key.Modifier.Control) =>

          case (Some(node), Key.Modifier.Shift) => visualizer.Selection.add(node)
          case (None, Key.Modifier.Shift) =>

          case (Some(node), _) =>
            visualizer.Selection.clear()
            visualizer.Selection.add(node)
          case (None, _) =>
            visualizer.Selection.clear()
        }
      }

      def right_click()
      {
        val menu = Popups(panel, find_visible_node(c), visualizer.Selection.get())
        menu.show(panel.peer, at.x, at.y)
      }

      if (clicks < 2) {
        if (SwingUtilities.isRightMouseButton(e.peer)) right_click()
        else left_click()
      }
    }

    def drag(info: (Point, List[Graph_Display.Node], List[(Graph_Display.Edge, Int)]), to: Point)
    {
      val (from, p, d) = info

      val s = Transform.scale_discrete
      val (dx, dy) = (to.x - from.x, to.y - from.y)
      (p, d) match {
        case (Nil, Nil) =>
          val r = panel.peer.getViewport.getViewRect
          r.translate(-dx, -dy)

          paint_panel.peer.scrollRectToVisible(r)

        case (Nil, ds) =>
          ds.foreach(d => visualizer.Coordinates.translate_dummy(d, dx / s, dy / s))

        case (ls, _) =>
          ls.foreach(l => visualizer.Coordinates.translate_node(l, dx / s, dy / s))
      }
    }
  }
}