separate module Metrics;
authorwenzelm
Mon, 05 Jan 2015 21:47:12 +0100
changeset 59290 569a8109eeb2
parent 59289 42710fe5f05a
child 59291 506660c6792f
separate module Metrics; maintain static metrics (with font) and visible_graph via layout;
src/Pure/build-jars
src/Tools/Graphview/graph_panel.scala
src/Tools/Graphview/layout.scala
src/Tools/Graphview/metrics.scala
src/Tools/Graphview/popups.scala
src/Tools/Graphview/shapes.scala
src/Tools/Graphview/visualizer.scala
src/Tools/jEdit/src/graphview_dockable.scala
--- a/src/Pure/build-jars	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Pure/build-jars	Mon Jan 05 21:47:12 2015 +0100
@@ -106,6 +106,7 @@
   "../Tools/Graphview/graph_panel.scala"
   "../Tools/Graphview/layout.scala"
   "../Tools/Graphview/main_panel.scala"
+  "../Tools/Graphview/metrics.scala"
   "../Tools/Graphview/model.scala"
   "../Tools/Graphview/mutator_dialog.scala"
   "../Tools/Graphview/mutator_event.scala"
--- a/src/Tools/Graphview/graph_panel.scala	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Tools/Graphview/graph_panel.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -12,7 +12,6 @@
 
 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}
@@ -46,11 +45,8 @@
   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))
-  }
+    visualizer.visible_graph.keys_iterator.find(node =>
+      visualizer.Drawer.shape(node).contains(at))
 
   def refresh()
   {
@@ -133,7 +129,7 @@
 
     def scale_discrete: Double =
     {
-      val font_height = GUI.line_metrics(visualizer.font()).getHeight.toInt
+      val font_height = GUI.line_metrics(visualizer.metrics.font).getHeight.toInt
       (scale * font_height).floor / font_height
     }
 
@@ -147,7 +143,7 @@
 
     def fit_to_window()
     {
-      if (visualizer.model.make_visible_graph().is_empty)
+      if (visualizer.visible_graph.is_empty)
         rescale(1.0)
       else {
         val box = visualizer.Coordinates.bounding_box()
@@ -181,18 +177,16 @@
 
     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))
-            }
+      visualizer.visible_graph.edges_iterator.map(edge =>
+        visualizer.Coordinates.get_dummies(edge).zipWithIndex.map((edge, _))).flatten.find(
+          {
+            case (_, (p, _)) =>
+              visualizer.Drawer.shape(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)
--- a/src/Tools/Graphview/layout.scala	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Tools/Graphview/layout.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -27,12 +27,13 @@
   private type Level = List[Key]
   private type Levels = List[Level]
 
-  val empty = new Layout(Map.empty, Map.empty)
+  val empty: Layout =
+    new Layout(Metrics.default, Graph_Display.empty_graph, Map.empty, Map.empty)
 
   val pendulum_iterations = 10
   val minimize_crossings_iterations = 40
 
-  def make(metrics: Visualizer.Metrics, visible_graph: Graph_Display.Graph): Layout =
+  def make(metrics: Metrics, visible_graph: Graph_Display.Graph): Layout =
   {
     if (visible_graph.is_empty) empty
     else {
@@ -69,7 +70,7 @@
           case (map, key) => map + (key -> dummies(key).map(coords(_)))
         }
 
-      new Layout(coords, dummy_coords)
+      new Layout(metrics, visible_graph, coords, dummy_coords)
     }
   }
 
@@ -170,7 +171,7 @@
   }
 
   def pendulum(
-    metrics: Visualizer.Metrics, graph: Graph_Display.Graph,
+    metrics: Metrics, graph: Graph_Display.Graph,
     levels: Levels, coords: Map[Key, Point]): Coordinates =
   {
     type Regions = List[List[Region]]
@@ -252,7 +253,7 @@
   {
     var nodes: List[Key] = List(node)
 
-    def distance(metrics: Visualizer.Metrics, coords: Coordinates, that: Region): Double =
+    def distance(metrics: Metrics, coords: Coordinates, that: Region): Double =
     {
       val n1 = this.nodes.last; val x1 = coords(n1).x + metrics.box_width2(n1)
       val n2 = that.nodes.head; val x2 = coords(n2).x - metrics.box_width2(n2)
@@ -278,26 +279,36 @@
 }
 
 final class Layout private(
+  val metrics: Metrics,
+  val visible_graph: Graph_Display.Graph,
   nodes: Map[Graph_Display.Node, Layout.Point],
   dummies: Map[Graph_Display.Edge, List[Layout.Point]])
 {
+  /* nodes */
+
   def get_node(node: Graph_Display.Node): Layout.Point =
     nodes.getOrElse(node, Layout.Point.zero)
 
   def map_node(node: Graph_Display.Node, f: Layout.Point => Layout.Point): Layout =
     nodes.get(node) match {
       case None => this
-      case Some(p) => new Layout(nodes + (node -> f(p)), dummies)
+      case Some(p) =>
+        val nodes1 = nodes + (node -> f(p))
+        new Layout(metrics, visible_graph, nodes1, dummies)
     }
 
 
+  /* dummies */
+
   def get_dummies(edge: Graph_Display.Edge): List[Layout.Point] =
     dummies.getOrElse(edge, Nil)
 
   def map_dummies(edge: Graph_Display.Edge, f: List[Layout.Point] => List[Layout.Point]): Layout =
     dummies.get(edge) match {
       case None => this
-      case Some(ds) => new Layout(nodes, dummies + (edge -> f(ds)))
+      case Some(ds) =>
+        val dummies1 = dummies + (edge -> f(ds))
+        new Layout(metrics, visible_graph, nodes, dummies1)
     }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Tools/Graphview/metrics.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -0,0 +1,45 @@
+/*  Title:      Tools/Graphview/metrics.scala
+    Author:     Makarius
+
+Physical metrics for layout and painting.
+*/
+
+package isabelle.graphview
+
+
+import isabelle._
+
+import java.awt.{Font, RenderingHints}
+import java.awt.font.FontRenderContext
+
+
+object Metrics
+{
+  val rendering_hints: RenderingHints =
+    new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
+
+  val font_render_context: FontRenderContext =
+    new FontRenderContext(null, true, false)
+
+  def apply(font: Font): Metrics = new Metrics(font)
+
+  val default: Metrics = apply(new Font("Helvetica", Font.PLAIN, 12))
+}
+
+class Metrics private(val font: Font)
+{
+  def string_bounds(s: String) = font.getStringBounds(s, Metrics.font_render_context)
+  private val mix = string_bounds("mix")
+  val space_width = string_bounds(" ").getWidth
+  def char_width: Double = mix.getWidth / 3
+  def height: Double = mix.getHeight
+  def ascent: Double = font.getLineMetrics("", Metrics.font_render_context).getAscent
+  def gap: Double = mix.getWidth
+  def pad: Double = char_width
+
+  def box_width2(node: Graph_Display.Node): Double =
+    ((string_bounds(node.toString).getWidth + pad) / 2).ceil
+  def box_gap: Double = gap.ceil
+  def box_height(n: Int): Double = (char_width * 1.5 * (5 max n)).ceil
+}
+
--- a/src/Tools/Graphview/popups.scala	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Tools/Graphview/popups.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -24,7 +24,7 @@
     val visualizer = panel.visualizer
 
     val add_mutator = visualizer.model.Mutators.add _
-    val visible_graph = visualizer.model.make_visible_graph()
+    val visible_graph = visualizer.visible_graph
 
     def filter_context(
         nodes: List[Graph_Display.Node], reverse: Boolean, caption: String, edges: Boolean) =
--- a/src/Tools/Graphview/shapes.scala	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Tools/Graphview/shapes.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -21,22 +21,22 @@
 
   object Node
   {
-    def shape(m: Visualizer.Metrics, visualizer: Visualizer, node: Graph_Display.Node)
-      : Rectangle2D.Double =
+    def shape(visualizer: Visualizer, node: Graph_Display.Node): Rectangle2D.Double =
     {
+      val metrics = visualizer.metrics
       val p = visualizer.Coordinates.get_node(node)
-      val bounds = m.string_bounds(node.toString)
-      val w = bounds.getWidth + m.pad
-      val h = bounds.getHeight + m.pad
+      val bounds = metrics.string_bounds(node.toString)
+      val w = bounds.getWidth + metrics.pad
+      val h = bounds.getHeight + metrics.pad
       new Rectangle2D.Double((p.x - (w / 2)).floor, (p.y - (h / 2)).floor, w.ceil, h.ceil)
     }
 
     def paint(gfx: Graphics2D, visualizer: Visualizer, node: Graph_Display.Node)
     {
-      val m = Visualizer.Metrics(gfx)
-      val s = shape(m, visualizer, node)
+      val metrics = visualizer.metrics
+      val s = shape(visualizer, node)
       val c = visualizer.node_color(node)
-      val bounds = m.string_bounds(node.toString)
+      val bounds = metrics.string_bounds(node.toString)
 
       gfx.setColor(c.background)
       gfx.fill(s)
@@ -46,9 +46,10 @@
       gfx.draw(s)
 
       gfx.setColor(c.foreground)
+      gfx.setFont(metrics.font)
       gfx.drawString(node.toString,
         (s.getCenterX - bounds.getWidth / 2).round.toInt,
-        (s.getCenterY - bounds.getHeight / 2 + m.ascent).round.toInt)
+        (s.getCenterY - bounds.getHeight / 2 + metrics.ascent).round.toInt)
     }
   }
 
@@ -56,8 +57,9 @@
   {
     private val identity = new AffineTransform()
 
-    def shape(m: Visualizer.Metrics, visualizer: Visualizer): Shape =
+    def shape(visualizer: Visualizer): Shape =
     {
+      val m = visualizer.metrics
       val w = (m.space_width / 2).ceil
       new Rectangle2D.Double(- w, - w, 2 * w, 2 * w)
     }
@@ -67,12 +69,9 @@
 
     def paint_transformed(gfx: Graphics2D, visualizer: Visualizer, at: AffineTransform)
     {
-      val m = Visualizer.Metrics(gfx)
-      val s = shape(m, visualizer)
-
       gfx.setStroke(default_stroke)
       gfx.setColor(visualizer.dummy_color)
-      gfx.draw(at.createTransformedShape(s))
+      gfx.draw(at.createTransformedShape(shape(visualizer)))
     }
   }
 
@@ -103,9 +102,7 @@
       gfx.setColor(visualizer.edge_color(edge))
       gfx.draw(path)
 
-      if (head)
-        Arrow_Head.paint(gfx, path,
-          visualizer.Drawer.shape(Visualizer.Metrics(gfx), edge._2))
+      if (head) Arrow_Head.paint(gfx, path, visualizer.Drawer.shape(edge._2))
     }
   }
 
@@ -160,9 +157,7 @@
         gfx.setColor(visualizer.edge_color(edge))
         gfx.draw(path)
 
-        if (head)
-          Arrow_Head.paint(gfx, path,
-            visualizer.Drawer.shape(Visualizer.Metrics(gfx), edge._2))
+        if (head) Arrow_Head.paint(gfx, path, visualizer.Drawer.shape(edge._2))
       }
     }
   }
--- a/src/Tools/Graphview/visualizer.scala	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Tools/Graphview/visualizer.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -10,46 +10,21 @@
 
 import isabelle._
 
-import java.awt.{Font, FontMetrics, Color, Shape, RenderingHints, Graphics2D}
-import java.awt.font.FontRenderContext
-import java.awt.image.BufferedImage
+import java.awt.{Font, Color, Shape, Graphics2D}
 import java.awt.geom.Rectangle2D
 import javax.swing.JComponent
 
 
-object Visualizer
-{
-  object Metrics
-  {
-    def apply(font: Font, font_render_context: FontRenderContext) =
-      new Metrics(font, font_render_context)
-
-    def apply(gfx: Graphics2D): Metrics =
-      new Metrics(gfx.getFont, gfx.getFontRenderContext)
-  }
-
-  class Metrics private(font: Font, font_render_context: FontRenderContext)
-  {
-    def string_bounds(s: String) = font.getStringBounds(s, font_render_context)
-    private val mix = string_bounds("mix")
-    val space_width = string_bounds(" ").getWidth
-    def char_width: Double = mix.getWidth / 3
-    def height: Double = mix.getHeight
-    def ascent: Double = font.getLineMetrics("", font_render_context).getAscent
-    def gap: Double = mix.getWidth
-    def pad: Double = char_width
-
-    def box_width2(node: Graph_Display.Node): Double =
-      ((string_bounds(node.toString).getWidth + pad) / 2).ceil
-    def box_gap: Double = gap.ceil
-    def box_height(n: Int): Double = (char_width * 1.5 * (5 max n)).ceil
-  }
-}
-
 class Visualizer(options: => Options, val model: Model)
 {
   visualizer =>
 
+  // owned by GUI thread
+  private var layout: Layout = Layout.empty
+
+  def metrics: Metrics = layout.metrics
+  def visible_graph: Graph_Display.Graph = layout.visible_graph
+
 
   /* tooltips */
 
@@ -67,23 +42,13 @@
 
   /* font rendering information */
 
-  def font(): Font =
+  def make_font(): Font =
   {
     val family = options.string("graphview_font_family")
     val size = options.int("graphview_font_size")
     new Font(family, Font.PLAIN, size)
   }
 
-  val rendering_hints =
-    new RenderingHints(
-      RenderingHints.KEY_ANTIALIASING,
-      RenderingHints.VALUE_ANTIALIAS_ON)
-
-  val font_render_context = new FontRenderContext(null, true, false)
-
-  def metrics(): Visualizer.Metrics =
-    Visualizer.Metrics(font(), font_render_context)
-
 
   /* rendering parameters */
 
@@ -112,9 +77,6 @@
 
   object Coordinates
   {
-    // owned by GUI thread
-    private var layout = Layout.empty
-
     def get_node(node: Graph_Display.Node): Layout.Point = layout.get_node(node)
     def get_dummies(edge: Graph_Display.Edge): List[Layout.Point] = layout.get_dummies(edge)
 
@@ -137,28 +99,31 @@
 
     def update_layout()
     {
+      val metrics = Metrics(make_font())
+      val visible_graph = model.make_visible_graph()
+
       // FIXME avoid expensive operation on GUI thread
-      layout = Layout.make(metrics(), model.make_visible_graph())
+      layout = Layout.make(metrics, visible_graph)
     }
 
     def bounding_box(): Rectangle2D.Double =
     {
-      val m = metrics()
       var x0 = 0.0
       var y0 = 0.0
       var x1 = 0.0
       var y1 = 0.0
-      for (node <- model.make_visible_graph().keys_iterator) {
-        val shape = Shapes.Node.shape(m, visualizer, node)
+      for (node <- visible_graph.keys_iterator) {
+        val shape = Shapes.Node.shape(visualizer, node)
         x0 = x0 min shape.getMinX
         y0 = y0 min shape.getMinY
         x1 = x1 max shape.getMaxX
         y1 = y1 max shape.getMaxY
       }
-      x0 = (x0 - m.gap).floor
-      y0 = (y0 - m.gap).floor
-      x1 = (x1 + m.gap).ceil
-      y1 = (y1 + m.gap).ceil
+      val gap = metrics.gap
+      x0 = (x0 - gap).floor
+      y0 = (y0 - gap).floor
+      x1 = (x1 + gap).ceil
+      y1 = (y1 + gap).ceil
       new Rectangle2D.Double(x0, y0, x1 - x0, y1 - y0)
     }
   }
@@ -173,16 +138,14 @@
 
     def paint_all_visible(gfx: Graphics2D, dummies: Boolean)
     {
-      gfx.setFont(font())
-      gfx.setRenderingHints(rendering_hints)
-      val visible_graph = model.make_visible_graph()
+      gfx.setRenderingHints(Metrics.rendering_hints)
       visible_graph.edges_iterator.foreach(apply(gfx, _, arrow_heads, dummies))
       visible_graph.keys_iterator.foreach(apply(gfx, _))
     }
 
-    def shape(m: Visualizer.Metrics, node: Graph_Display.Node): Shape =
-      if (node.is_dummy) Shapes.Dummy.shape(m, visualizer)
-      else Shapes.Node.shape(m, visualizer, node)
+    def shape(node: Graph_Display.Node): Shape =
+      if (node.is_dummy) Shapes.Dummy.shape(visualizer)
+      else Shapes.Node.shape(visualizer, node)
   }
 
   object Selection
--- a/src/Tools/jEdit/src/graphview_dockable.scala	Mon Jan 05 21:44:05 2015 +0100
+++ b/src/Tools/jEdit/src/graphview_dockable.scala	Mon Jan 05 21:47:12 2015 +0100
@@ -78,7 +78,7 @@
           override def selection_color = view.getTextArea.getPainter.getSelectionColor
           override def error_color = PIDE.options.color_value("error_color")
 
-          override def font() =
+          override def make_font(): Font =
             GUI.imitate_font(Font_Info.main().font,
               PIDE.options.string("graphview_font_family"),
               PIDE.options.real("graphview_font_scale"))