| author | wenzelm | 
| Mon, 18 Nov 2024 15:05:31 +0100 | |
| changeset 81483 | 7d4df25af572 | 
| parent 81398 | f92ea68473f2 | 
| child 82142 | 508a673c87ac | 
| permissions | -rw-r--r-- | 
| 59202 | 1 | /* Title: Tools/Graphview/graph_panel.scala | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 2 | Author: Markus Kaiser, TU Muenchen | 
| 59240 | 3 | Author: Makarius | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 4 | |
| 59461 | 5 | GUI panel for graph layout. | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 6 | */ | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 7 | |
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 8 | package isabelle.graphview | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 9 | |
| 55618 | 10 | |
| 49558 | 11 | import isabelle._ | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 12 | |
| 59441 | 13 | import java.awt.{Dimension, Graphics2D, Point, Rectangle}
 | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 14 | import java.awt.geom.{AffineTransform, Point2D}
 | 
| 59408 | 15 | import javax.imageio.ImageIO | 
| 59225 | 16 | import javax.swing.{JScrollPane, JComponent, SwingUtilities}
 | 
| 59408 | 17 | import javax.swing.border.EmptyBorder | 
| 49729 | 18 | |
| 59408 | 19 | import scala.swing.{BorderPanel, Button, CheckBox, Action, FileChooser, Panel, ScrollPane}
 | 
| 59253 | 20 | import scala.swing.event.{Event, Key, MousePressed, MouseDragged, MouseClicked, MouseEvent}
 | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 21 | |
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 22 | |
| 75393 | 23 | class Graph_Panel(val graphview: Graphview) extends BorderPanel {
 | 
| 59408 | 24 | graph_panel => | 
| 25 | ||
| 49729 | 26 | |
| 59398 | 27 | |
| 59408 | 28 | /** graph **/ | 
| 59398 | 29 | |
| 59408 | 30 | /* painter */ | 
| 59243 
21ef04bd4e17
recovered tooltip from 6e77ddb1e3fb: non-null default is required as prerequisite;
 wenzelm parents: 
59241diff
changeset | 31 | |
| 75393 | 32 |   private val painter = new Panel {
 | 
| 33 |     override def paint(gfx: Graphics2D): Unit = {
 | |
| 59408 | 34 | super.paintComponent(gfx) | 
| 35 | ||
| 59459 | 36 | gfx.setColor(graphview.background_color) | 
| 59408 | 37 | gfx.fillRect(0, 0, peer.getWidth, peer.getHeight) | 
| 38 | ||
| 39 | gfx.transform(Transform()) | |
| 59460 | 40 | graphview.paint(gfx) | 
| 59408 | 41 | } | 
| 49729 | 42 | } | 
| 43 | ||
| 75393 | 44 |   def set_preferred_size(): Unit = {
 | 
| 59459 | 45 | val box = graphview.bounding_box() | 
| 59408 | 46 | val s = Transform.scale_discrete | 
| 47 | painter.preferredSize = new Dimension((box.width * s).ceil.toInt, (box.height * s).ceil.toInt) | |
| 48 | painter.revalidate() | |
| 49 | } | |
| 59398 | 50 | |
| 75393 | 51 |   def refresh(): Unit = {
 | 
| 59408 | 52 |     if (painter != null) {
 | 
| 53 | set_preferred_size() | |
| 54 | painter.repaint() | |
| 55 | } | |
| 56 | } | |
| 59237 
ac135eff1ffb
clarified mouse wheel: conventional scrolling, not scaling;
 wenzelm parents: 
59234diff
changeset | 57 | |
| 75393 | 58 |   def scroll_to_node(node: Graph_Display.Node): Unit = {
 | 
| 59459 | 59 | val gap = graphview.metrics.gap | 
| 60 | val info = graphview.layout.get_node(node) | |
| 59398 | 61 | |
| 62 | val t = Transform() | |
| 63 | val p = | |
| 64 | t.transform(new Point2D.Double(info.x - info.width2 - gap, info.y - info.height2 - gap), null) | |
| 65 | val q = | |
| 66 | t.transform(new Point2D.Double(info.x + info.width2 + gap, info.y + info.height2 + gap), null) | |
| 67 | ||
| 59408 | 68 | painter.peer.scrollRectToVisible( | 
| 59398 | 69 | new Rectangle(p.getX.toInt, p.getY.toInt, | 
| 70 | (q.getX - p.getX).ceil.toInt, (q.getY - p.getY).ceil.toInt)) | |
| 49735 
30e2f3f1c623
more precise repaint and revalidate -- the latter is important to keep in sync with content update;
 wenzelm parents: 
49733diff
changeset | 71 | } | 
| 
30e2f3f1c623
more precise repaint and revalidate -- the latter is important to keep in sync with content update;
 wenzelm parents: 
49733diff
changeset | 72 | |
| 50470 | 73 | |
| 59408 | 74 | /* scrollable graph pane */ | 
| 75 | ||
| 76 |   private val graph_pane: ScrollPane = new ScrollPane(painter) {
 | |
| 77 | tooltip = "" | |
| 50478 | 78 | |
| 59408 | 79 |     override lazy val peer: JScrollPane = new JScrollPane with SuperMixin {
 | 
| 80 | override def getToolTipText(event: java.awt.event.MouseEvent): String = | |
| 59459 | 81 |         graphview.find_node(Transform.pane_to_graph_coordinates(event.getPoint)) match {
 | 
| 59408 | 82 | case Some(node) => | 
| 59459 | 83 |             graphview.model.full_graph.get_node(node) match {
 | 
| 59408 | 84 | case Nil => null | 
| 81398 
f92ea68473f2
clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
 wenzelm parents: 
81382diff
changeset | 85 | case List(tip: XML.Elem) => | 
| 
f92ea68473f2
clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
 wenzelm parents: 
81382diff
changeset | 86 | graphview.make_tooltip(graph_pane.peer, event.getX, event.getY, tip) | 
| 
f92ea68473f2
clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
 wenzelm parents: 
81382diff
changeset | 87 | case body => | 
| 
f92ea68473f2
clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
 wenzelm parents: 
81382diff
changeset | 88 | val tip = Pretty.block(body, indent = 0) | 
| 
f92ea68473f2
clarified signature with subtle change of semantics: output consists of individual messages that are formatted (and separated) internally;
 wenzelm parents: 
81382diff
changeset | 89 | graphview.make_tooltip(graph_pane.peer, event.getX, event.getY, tip) | 
| 59408 | 90 | } | 
| 91 | case None => null | |
| 92 | } | |
| 59218 | 93 | } | 
| 50470 | 94 | |
| 59408 | 95 | horizontalScrollBarPolicy = ScrollPane.BarPolicy.Always | 
| 96 | verticalScrollBarPolicy = ScrollPane.BarPolicy.Always | |
| 97 | ||
| 98 | listenTo(mouse.moves) | |
| 99 | listenTo(mouse.clicks) | |
| 100 | reactions += | |
| 75393 | 101 |       {
 | 
| 102 | case MousePressed(_, p, _, _, _) => | |
| 103 | Mouse_Interaction.pressed(p) | |
| 104 | painter.repaint() | |
| 105 | case MouseDragged(_, to, _) => | |
| 106 | Mouse_Interaction.dragged(to) | |
| 107 | painter.repaint() | |
| 108 | case e @ MouseClicked(_, p, m, n, _) => | |
| 109 | Mouse_Interaction.clicked(p, m, n, SwingUtilities.isRightMouseButton(e.peer)) | |
| 110 | painter.repaint() | |
| 111 | } | |
| 50470 | 112 | |
| 59408 | 113 | contents = painter | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 114 | } | 
| 59408 | 115 | graph_pane.peer.getVerticalScrollBar.setUnitIncrement(10) | 
| 50470 | 116 | |
| 59408 | 117 | |
| 118 | /* transform */ | |
| 49733 
38a68e6593be
prefer synchronous Mutator_Event.Bus on Swing_Thread;
 wenzelm parents: 
49732diff
changeset | 119 | |
| 75393 | 120 |   def rescale(s: Double): Unit = {
 | 
| 59398 | 121 | Transform.scale = s | 
| 122 | if (zoom != null) zoom.set_item((Transform.scale_discrete * 100).floor.toInt) | |
| 123 | refresh() | |
| 124 | } | |
| 50470 | 125 | |
| 75393 | 126 |   def fit_to_window(): Unit = {
 | 
| 59399 | 127 | Transform.fit_to_window() | 
| 128 | refresh() | |
| 129 | } | |
| 130 | ||
| 75393 | 131 |   private object Transform {
 | 
| 50474 
6ee044e2d1a7
initial layout coordinates more like old browser;
 wenzelm parents: 
50470diff
changeset | 132 | private var _scale: Double = 1.0 | 
| 50477 | 133 | def scale: Double = _scale | 
| 75393 | 134 |     def scale_=(s: Double): Unit = {
 | 
| 59241 
541b95e94dc7
clarified bounding box, similar to old graph browser;
 wenzelm parents: 
59240diff
changeset | 135 | _scale = (s min 10.0) max 0.1 | 
| 50468 | 136 | } | 
| 59255 
db265648139c
clarified fit_to_window: floor scale within window bounds;
 wenzelm parents: 
59253diff
changeset | 137 | |
| 75393 | 138 |     def scale_discrete: Double = {
 | 
| 59459 | 139 | val font_height = GUI.line_metrics(graphview.metrics.font).getHeight.toInt | 
| 59286 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59262diff
changeset | 140 | (scale * font_height).floor / font_height | 
| 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59262diff
changeset | 141 | } | 
| 50470 | 142 | |
| 75393 | 143 |     def apply(): AffineTransform = {
 | 
| 59459 | 144 | val box = graphview.bounding_box() | 
| 59397 | 145 | val t = AffineTransform.getScaleInstance(scale_discrete, scale_discrete) | 
| 146 | t.translate(- box.x, - box.y) | |
| 147 | t | |
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 148 | } | 
| 50470 | 149 | |
| 75393 | 150 |     def fit_to_window(): Unit = {
 | 
| 59459 | 151 | if (graphview.visible_graph.is_empty) | 
| 50491 | 152 | rescale(1.0) | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 153 |       else {
 | 
| 59459 | 154 | val box = graphview.bounding_box() | 
| 59241 
541b95e94dc7
clarified bounding box, similar to old graph browser;
 wenzelm parents: 
59240diff
changeset | 155 | rescale((size.width / box.width) min (size.height / box.height)) | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 156 | } | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 157 | } | 
| 50470 | 158 | |
| 75393 | 159 |     def pane_to_graph_coordinates(at: Point2D): Point2D = {
 | 
| 50477 | 160 | val s = Transform.scale_discrete | 
| 59408 | 161 | val p = Transform().inverseTransform(graph_pane.peer.getViewport.getViewPosition, null) | 
| 50470 | 162 | |
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 163 | p.setLocation(p.getX + at.getX / s, p.getY + at.getY / s) | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 164 | p | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 165 | } | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 166 | } | 
| 50470 | 167 | |
| 59398 | 168 | |
| 169 | /* interaction */ | |
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 170 | |
| 59459 | 171 |   graphview.model.Colors.events += { case _ => painter.repaint() }
 | 
| 172 |   graphview.model.Mutators.events += { case _ => painter.repaint() }
 | |
| 59398 | 173 | |
| 75393 | 174 |   private object Mouse_Interaction {
 | 
| 59398 | 175 | private var draginfo: (Point, List[Graph_Display.Node], List[Layout.Dummy]) = | 
| 176 | (new Point(0, 0), Nil, Nil) | |
| 59253 | 177 | |
| 75393 | 178 |     def pressed(at: Point): Unit = {
 | 
| 59253 | 179 | val c = Transform.pane_to_graph_coordinates(at) | 
| 180 | val l = | |
| 59459 | 181 |         graphview.find_node(c) match {
 | 
| 59253 | 182 | case Some(node) => | 
| 59459 | 183 | if (graphview.Selection.contains(node)) graphview.Selection.get() | 
| 59253 | 184 | else List(node) | 
| 185 | case None => Nil | |
| 186 | } | |
| 187 | val d = | |
| 188 |         l match {
 | |
| 59459 | 189 | case Nil => graphview.find_dummy(c).toList | 
| 59253 | 190 | case _ => Nil | 
| 191 | } | |
| 192 | draginfo = (at, l, d) | |
| 193 | } | |
| 50470 | 194 | |
| 75393 | 195 |     def dragged(to: Point): Unit = {
 | 
| 59398 | 196 | val (from, p, d) = draginfo | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 197 | |
| 59253 | 198 | val s = Transform.scale_discrete | 
| 59398 | 199 | val dx = to.x - from.x | 
| 200 | val dy = to.y - from.y | |
| 201 | ||
| 59253 | 202 |       (p, d) match {
 | 
| 203 | case (Nil, Nil) => | |
| 59408 | 204 | val r = graph_pane.peer.getViewport.getViewRect | 
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 205 | r.translate(- dx, - dy) | 
| 59408 | 206 | painter.peer.scrollRectToVisible(r) | 
| 50470 | 207 | |
| 59253 | 208 | case (Nil, ds) => | 
| 59459 | 209 | ds.foreach(d => graphview.translate_vertex(d, dx / s, dy / s)) | 
| 59253 | 210 | |
| 211 | case (ls, _) => | |
| 59459 | 212 | ls.foreach(l => graphview.translate_vertex(Layout.Node(l), dx / s, dy / s)) | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 213 | } | 
| 59398 | 214 | |
| 215 | draginfo = (to, p, d) | |
| 216 | } | |
| 217 | ||
| 75393 | 218 |     def clicked(at: Point, m: Key.Modifiers, clicks: Int, right_click: Boolean): Unit = {
 | 
| 59398 | 219 | val c = Transform.pane_to_graph_coordinates(at) | 
| 220 | ||
| 221 |       if (clicks < 2) {
 | |
| 222 |         if (right_click) {
 | |
| 59403 | 223 | // FIXME | 
| 224 |           if (false) {
 | |
| 59459 | 225 | val menu = Popups(graph_panel, graphview.find_node(c), graphview.Selection.get()) | 
| 59408 | 226 | menu.show(graph_pane.peer, at.x, at.y) | 
| 59403 | 227 | } | 
| 59398 | 228 | } | 
| 229 |         else {
 | |
| 59459 | 230 |           (graphview.find_node(c), m) match {
 | 
| 231 | case (Some(node), Key.Modifier.Control) => graphview.Selection.add(node) | |
| 59398 | 232 | case (None, Key.Modifier.Control) => | 
| 233 | ||
| 59459 | 234 | case (Some(node), Key.Modifier.Shift) => graphview.Selection.add(node) | 
| 59398 | 235 | case (None, Key.Modifier.Shift) => | 
| 236 | ||
| 237 | case (Some(node), _) => | |
| 59459 | 238 | graphview.Selection.clear() | 
| 239 | graphview.Selection.add(node) | |
| 59398 | 240 | case (None, _) => | 
| 59459 | 241 | graphview.Selection.clear() | 
| 59398 | 242 | } | 
| 243 | } | |
| 244 | } | |
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 245 | } | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 246 | } | 
| 59408 | 247 | |
| 248 | ||
| 249 | ||
| 250 | /** controls **/ | |
| 251 | ||
| 252 | private val mutator_dialog = | |
| 59459 | 253 | new Mutator_Dialog(graphview, graphview.model.Mutators, "Filters", "Hide", false) | 
| 59408 | 254 | private val color_dialog = | 
| 59459 | 255 | new Mutator_Dialog(graphview, graphview.model.Colors, "Colorations") | 
| 59408 | 256 | |
| 257 |   private val chooser = new FileChooser {
 | |
| 258 | fileSelectionMode = FileChooser.SelectionMode.FilesOnly | |
| 259 | title = "Save image (.png or .pdf)" | |
| 260 | } | |
| 261 | ||
| 59409 | 262 |   private val show_content = new CheckBox() {
 | 
| 59459 | 263 | selected = graphview.show_content | 
| 59409 | 264 |     action = Action("Show content") {
 | 
| 59459 | 265 | graphview.show_content = selected | 
| 266 | graphview.update_layout() | |
| 59409 | 267 | refresh() | 
| 268 | } | |
| 269 | tooltip = "Show full node content within graph layout" | |
| 270 | } | |
| 271 | ||
| 272 |   private val show_arrow_heads = new CheckBox() {
 | |
| 59459 | 273 | selected = graphview.show_arrow_heads | 
| 59409 | 274 |     action = Action("Show arrow heads") {
 | 
| 59459 | 275 | graphview.show_arrow_heads = selected | 
| 59409 | 276 | painter.repaint() | 
| 277 | } | |
| 278 | tooltip = "Draw edges with explicit arrow heads" | |
| 279 | } | |
| 280 | ||
| 281 |   private val show_dummies = new CheckBox() {
 | |
| 59459 | 282 | selected = graphview.show_dummies | 
| 59409 | 283 |     action = Action("Show dummies") {
 | 
| 59459 | 284 | graphview.show_dummies = selected | 
| 59409 | 285 | painter.repaint() | 
| 286 | } | |
| 287 | tooltip = "Draw dummy nodes within graph layout, for easier mouse dragging" | |
| 288 | } | |
| 289 | ||
| 290 |   private val save_image = new Button {
 | |
| 291 |     action = Action("Save image") {
 | |
| 292 |       chooser.showSaveDialog(this) match {
 | |
| 59441 | 293 | case FileChooser.Result.Approve => | 
| 59459 | 294 |           try { Graph_File.write(chooser.selectedFile, graphview) }
 | 
| 59441 | 295 |           catch {
 | 
| 296 | case ERROR(msg) => GUI.error_dialog(this.peer, "Error", GUI.scrollable_text(msg)) | |
| 297 | } | |
| 59409 | 298 | case _ => | 
| 299 | } | |
| 300 | } | |
| 301 | tooltip = "Save current graph layout as PNG or PDF" | |
| 302 | } | |
| 303 | ||
| 81382 | 304 |   private val zoom = new GUI.Zoom { override def changed(): Unit = rescale(scale) }
 | 
| 59409 | 305 | |
| 306 |   private val fit_window = new Button {
 | |
| 307 |     action = Action("Fit to window") { fit_to_window() }
 | |
| 308 | tooltip = "Zoom to fit window width and height" | |
| 309 | } | |
| 310 | ||
| 311 |   private val update_layout = new Button {
 | |
| 312 |     action = Action("Update layout") {
 | |
| 59459 | 313 | graphview.update_layout() | 
| 59409 | 314 | refresh() | 
| 315 | } | |
| 316 | tooltip = "Regenerate graph layout according to built-in algorithm" | |
| 317 | } | |
| 318 | ||
| 61176 | 319 |   private val editor_style = new CheckBox() {
 | 
| 320 | selected = graphview.editor_style | |
| 321 |     action = Action("Editor style") {
 | |
| 322 | graphview.editor_style = selected | |
| 323 | graphview.update_layout() | |
| 324 | refresh() | |
| 325 | } | |
| 326 | tooltip = "Use editor font and colors for painting" | |
| 327 | } | |
| 328 | ||
| 73367 | 329 |   private val colorations = new Button { action = Action("Colorations") { color_dialog.open() } }
 | 
| 330 |   private val filters = new Button { action = Action("Filters") { mutator_dialog.open() } }
 | |
| 59409 | 331 | |
| 59408 | 332 | private val controls = | 
| 66205 | 333 | Wrap_Panel( | 
| 334 | List(show_content, show_arrow_heads, show_dummies, | |
| 66206 | 335 | save_image, zoom, fit_window, update_layout, editor_style)) // FIXME colorations, filters | 
| 59408 | 336 | |
| 337 | ||
| 338 | ||
| 339 | /** main layout **/ | |
| 340 | ||
| 341 | layout(graph_pane) = BorderPanel.Position.Center | |
| 342 | layout(controls) = BorderPanel.Position.North | |
| 343 | ||
| 344 | rescale(1.0) | |
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 345 | } |