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