-
Notifications
You must be signed in to change notification settings - Fork 18
/
DrawExpansionTree.scala
576 lines (510 loc) · 20.2 KB
/
DrawExpansionTree.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
package gapt.prooftool
import gapt.expr._
import swing._
import scala.swing.event.{ MouseClicked, MouseEntered, MouseExited }
import java.awt.{ Color, Font }
import java.awt.event.MouseEvent
import gapt.proofs.expansion._
import org.scilab.forge.jlatexmath.{ TeXConstants, TeXFormula }
import java.awt.image.BufferedImage
import gapt.expr.formula.All
import gapt.expr.formula.And
import gapt.expr.formula.Ex
import gapt.expr.formula.Formula
import gapt.expr.formula.Imp
import gapt.expr.formula.Neg
import gapt.expr.formula.Or
import gapt.formats.latex.LatexExporter
import gapt.utils.ExceptionTag
object ExpansionTreeState extends Enumeration {
val Closed, Open, Expanded = Value
}
/**
* Draws an expansion tree. Abstract because most of the work is done by subclasses for quantifier and non-quantifier nodes.
* @param main The main prooftool window that this belongs to.
* @param expansionTree The expansion tree being displayed.
* @param outerQuantifier The object drawing the innermost enclosing quantifier (if any).
*/
abstract class DrawExpansionTree(
val main: ProofToolViewer[_],
val expansionTree: ExpansionTree,
val outerQuantifier: Option[DrawETQuantifierBlock] = None ) extends BoxPanel( Orientation.Horizontal ) {
background = new Color( 255, 255, 255 )
yLayoutAlignment = 0.5
xLayoutAlignment = 0
val highlightColor = Color.red
val ft = main.font
def subTrees: Vector[DrawExpansionTree]
def drawFormula( formula: Formula ): BoxPanel = new BoxPanel( Orientation.Horizontal ) {
background = new Color( 255, 255, 255 )
yLayoutAlignment = 0.5
opaque = false
formula match {
case Neg( f ) =>
val conn = label( "¬" )
val subF = drawFormula( f )
this.listenTo( conn.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
}
contents += conn
contents += subF
case And( f1, f2 ) =>
val parenthesis = connectedParentheses()
val conn = label( "∧" )
val subF1 = drawFormula( f1 )
val subF2 = drawFormula( f2 )
this.listenTo( conn.mouse.moves, parenthesis._1.mouse.moves, parenthesis._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parenthesis._1.foreground = highlightColor
parenthesis._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parenthesis._1.foreground = Color.black
parenthesis._2.foreground = Color.black
}
contents += parenthesis._1
contents += subF1
contents += conn
contents += subF2
contents += parenthesis._2
case Or( f1, f2 ) =>
val parenthesis = connectedParentheses()
val conn = label( "∨" )
val subF1 = drawFormula( f1 )
val subF2 = drawFormula( f2 )
this.listenTo( conn.mouse.moves, parenthesis._1.mouse.moves, parenthesis._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parenthesis._1.foreground = highlightColor
parenthesis._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parenthesis._1.foreground = Color.black
parenthesis._2.foreground = Color.black
}
contents += parenthesis._1
contents += subF1
contents += conn
contents += subF2
contents += parenthesis._2
case Imp( f1, f2 ) =>
val parenthesis = connectedParentheses()
val conn = label( "→" )
val subF1 = drawFormula( f1 )
val subF2 = drawFormula( f2 )
this.listenTo( conn.mouse.moves, parenthesis._1.mouse.moves, parenthesis._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parenthesis._1.foreground = highlightColor
parenthesis._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parenthesis._1.foreground = Color.black
parenthesis._2.foreground = Color.black
}
contents += parenthesis._1
contents += subF1
contents += conn
contents += subF2
contents += parenthesis._2
case Ex( v, f ) =>
contents += label( "\\exists " + LatexExporter.escapeName( v.name ) + " " )
contents += drawFormula( f )
case All( v, f ) =>
contents += label( "\\forall " + LatexExporter.escapeName( v.name ) + " " )
contents += drawFormula( f )
case _ =>
val lbl = LatexLabel( main, LatexExporter( formula ) )
lbl.deafTo( lbl.mouse.moves, lbl.mouse.clicks )
contents += lbl
}
}
def label( s: String ) = if ( s == "," )
new CommaLabel( main )
else new LatexLabel( main, s ) {
if ( s == "(" || s == ")" ) {
opaque = true
tooltip = "Click to mark/unmark."
listenTo( mouse.clicks )
reactions += {
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON3 =>
val color = RGBColorChooser( this, e.point.x, e.point.y )
if ( color.isDefined ) {
background = color.get
peer.dispatchEvent( new MouseEvent( peer, e.peer.getID, e.peer.getWhen, e.modifiers, e.point.x, e.point.y, e.clicks, e.triggersPopup, MouseEvent.BUTTON1 ) )
}
}
}
}
def connectedParentheses() = {
val left = label( "(" )
val right = label( ")" )
left.reactions += {
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON1 =>
if ( left.background == Color.white || left.background != left.background ) {
left.background = left.background
right.background = left.background
} else {
left.background = Color.white
right.background = Color.white
}
}
right.reactions += {
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON1 =>
if ( right.background == Color.white || right.background != right.background ) {
left.background = right.background
right.background = right.background
} else {
left.background = Color.white
right.background = Color.white
}
}
( left, right )
}
/**
* Expands this node (if it's a quantifier node) and all below it.
*/
def expandAll(): Unit = subTrees.foreach( _.expandAll() )
/**
* Closes this node (if it's a quantifier node) and all below it.
*/
def closeAll(): Unit = subTrees.foreach( _.closeAll() )
/**
* Opens this node (if it's a quantifier node) and all below it.
*/
def openAll(): Unit = subTrees.foreach( _.openAll() )
}
/**
* Factory object.
*/
object DrawExpansionTree {
def apply(
main: ProofToolViewer[_],
expansionTree: ExpansionTree,
outerQuantifier: Option[DrawETQuantifierBlock] = None ): DrawExpansionTree = expansionTree match {
case ETQuantifierBlock( _, _, _ ) =>
new DrawETQuantifierBlock( main, expansionTree, outerQuantifier )
case _ =>
new DrawETNonQuantifier( main, expansionTree, outerQuantifier )
}
}
/**
* Draws an expansion tree beginning with a quantifier block.
* @param main The main prooftool window that this belongs to.
* @param expansionTree The expansion tree being displayed.
* @param outerQuantifier The object drawing the quantifier imeediately outside this one (if any).
*/
class DrawETQuantifierBlock(
main: ProofToolViewer[_],
expansionTree: ExpansionTree,
outerQuantifier: Option[DrawETQuantifierBlock] = None ) extends DrawExpansionTree( main, expansionTree, outerQuantifier ) {
import ExpansionTreeState._
val ETQuantifierBlock( formula, depth, instances ) = expansionTree
val terms = instances.toVector.map( i => i._1 )
val subTrees = for ( et <- instances.toVector.map( i => i._2 ) ) yield DrawExpansionTree( main, et, Some( this ) )
val quantifiers = quantifierBlockString( takeQuants( formula, depth ), formula ) // quantifiers is a string containing the quantifier block represented by this weak node.
val subF = dropQuants( formula, depth )
val headLabelExpanded = formula match {
case Ex( _, _ ) => LatexLabel( main, "\\bigvee" )
case All( _, _ ) => LatexLabel( main, "\\bigwedge" )
case _ => throw new Exception( "Something went wrong in DrawExpansionTree!" )
}
headLabelExpanded.reactions += {
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON1 => close()
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON3 =>
PopupMenu( DrawETQuantifierBlock.this, headLabelExpanded, e.point.x, e.point.y )
}
private var _headLabel = LatexLabel( main, quantifiers ) // Head symbol of the expansion tree. Quantifiers if open or closed, bigvee/bigwedge if expanded.
def headLabel = _headLabel
def headLabel_=( newLabel: LatexLabel ) = {
_headLabel = newLabel
contents( 0 ) = newLabel
}
val termPanel = drawTerms( terms )
private var _instancesPanel = drawFormula( subF ) // Panel containing the instances.
def instancesPanel = _instancesPanel
def instancesPanel_=( newPanel: BoxPanel ) = {
_instancesPanel = newPanel
contents( 2 ) = newPanel
}
var state: ExpansionTreeState.Value = Open // The state of this quantifier node.
contents += headLabel
contents += termPanel
contents += instancesPanel
close()
def close(): Unit = {
if ( state != Closed ) {
state = Closed
headLabel = LatexLabel( main, quantifiers )
headLabel.reactions += {
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON3 =>
PopupMenu( this, headLabel, e.point.x, e.point.y )
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON1 =>
if ( state == Open ) expand()
else open()
}
termPanel.visible = false
instancesPanel = drawFormula( subF )
revalidate()
}
}
def open(): Unit = {
if ( state != Open ) {
outerQuantifier.foreach { _.expand() }
state = Open
headLabel = LatexLabel( main, quantifiers )
headLabel.reactions += {
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON3 =>
PopupMenu( this, headLabel, e.point.x, e.point.y )
case e: MouseClicked if e.peer.getButton == MouseEvent.BUTTON1 =>
if ( state == Open ) expand()
else open()
}
termPanel.visible = true
instancesPanel = drawFormula( subF )
revalidate()
}
}
def expand(): Unit = {
if ( state != Expanded ) {
outerQuantifier.foreach { _.expand() }
state = Expanded
if ( subTrees != Nil ) {
headLabel = headLabelExpanded
termPanel.visible = false
instancesPanel = subtreeMatrix
} else {
headLabel.visible = false
termPanel.visible = false
instancesPanel = drawFormula( instances.head._2.shallow ) // If there are no terms to expand the quantifier with, we can safely call drawFormula.
}
revalidate()
}
}
/**
*
* @return A BoxPanel containing the subtrees stacked on top of each other and surrounded by angle brackets.
*/
def subtreeMatrix = new BoxPanel( Orientation.Vertical ) {
background = new Color( 255, 255, 255 )
yLayoutAlignment = 0.5
border = Swing.EmptyBorder( 0, ft.getSize, 0, ft.getSize )
subTrees.foreach( bp => {
bp.border = Swing.EmptyBorder( 3 )
contents += bp
} )
override def paintComponent( g: Graphics2D ): Unit = {
import java.awt.{ BasicStroke, RenderingHints }
super.paintComponent( g )
val fSize = ft.getSize
val strokeSize = if ( fSize / 25 < 1 ) 1 else ft.getSize / 25
g.setStroke( new BasicStroke( strokeSize.toFloat, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) )
g.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB )
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON )
val leftAngleNodeX = fSize / 3
val leftAngleEdgesX = fSize - 1
val rightAngleNodeX = size.width - fSize / 3
val rightAngleEdgesX = size.width - ( fSize - 1 )
val anglesEdge1Y = 0
val anglesNodeY = size.height / 2
val anglesEdge2Y = size.height
g.drawLine( leftAngleNodeX, anglesNodeY, leftAngleEdgesX, anglesEdge1Y )
g.drawLine( leftAngleNodeX, anglesNodeY, leftAngleEdgesX, anglesEdge2Y )
g.drawLine( rightAngleNodeX, anglesNodeY, rightAngleEdgesX, anglesEdge1Y )
g.drawLine( rightAngleNodeX, anglesNodeY, rightAngleEdgesX, anglesEdge2Y )
}
}
/**
* @param vars
* @param f
* @return A string containing the quantifier block represented by this quantifier node.
*/
def quantifierBlockString( vars: List[Var], f: Formula ): String = f match {
case All( _, _ ) => vars.map( v => "\\forall " + LatexExporter( v ) + "\\:" ).mkString
case Ex( _, _ ) => vars.map( v => "\\exists " + LatexExporter( v ) + "\\:" ).mkString
}
def dropQuants( formula: Formula, howMany: Int ): Formula =
if ( howMany == 0 ) formula
else formula match {
case All( _, f ) => dropQuants( f, howMany - 1 )
case Ex( _, f ) => dropQuants( f, howMany - 1 )
}
def takeQuants( formula: Formula, howMany: Int ): List[Var] =
if ( howMany == 0 ) Nil
else formula match {
case All( x, f ) => x +: takeQuants( f, howMany - 1 )
case Ex( x, f ) => x +: takeQuants( f, howMany - 1 )
}
def drawTerms( list: Seq[Seq[Expr]] ) = new BoxPanel( Orientation.Vertical ) {
background = new Color( 255, 255, 255 )
yLayoutAlignment = 0.5
for ( v <- list )
contents += drawTermVector( v )
}
def drawTermVector( list: Seq[Expr] ) = new BoxPanel( Orientation.Horizontal ) {
background = new Color( 255, 255, 255 )
yLayoutAlignment = 0.5
contents += label( "\\langle" )
var firstTerm = true
for ( t <- list ) {
if ( !firstTerm ) {
val lbl = label( "," )
//lbl.yLayoutAlignment = 0
contents += lbl
} else firstTerm = false
contents += label( LatexExporter( t ) )
}
contents += label( "\\rangle" )
}
override def expandAll() = {
expand()
super.expandAll()
}
override def closeAll() = close()
override def openAll() = open()
}
/**
* Draws an expansion tree not beginning with a quantifier block.
* @param main The main prooftool window that this belongs to.
* @param expansionTree The expansion tree being displayed.
* @param outerQuantifier The object drawing the quantifier immediately outside this one (if any).
*/
class DrawETNonQuantifier(
main: ProofToolViewer[_],
expansionTree: ExpansionTree,
outerQuantifier: Option[DrawETQuantifierBlock] = None ) extends DrawExpansionTree( main, expansionTree, outerQuantifier ) {
override val subTrees = expansionTree match {
case ETAtom( _, _ ) | ETTop( _ ) | ETBottom( _ ) | ETWeakening( _, _ ) | ETDefinition( _, _ ) =>
val lbl = LatexLabel( main, LatexExporter( expansionTree.shallow ) )
lbl.deafTo( lbl.mouse.moves, lbl.mouse.clicks ) // We don't want atoms to react to mouse behavior.
contents += lbl
Vector()
case ETNeg( t ) =>
val conn = label( "¬" )
val subF = DrawExpansionTree( main, t, outerQuantifier )
this.listenTo( conn.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
}
contents += conn
contents += subF
Vector( subF )
case ETAnd( t1, t2 ) =>
val parentheses = connectedParentheses()
val conn = label( "∧" )
val subF1 = DrawExpansionTree( main, t1, outerQuantifier )
val subF2 = DrawExpansionTree( main, t2, outerQuantifier )
this.listenTo( conn.mouse.moves, parentheses._1.mouse.moves, parentheses._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parentheses._1.foreground = highlightColor
parentheses._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parentheses._1.foreground = Color.black
parentheses._2.foreground = Color.black
}
contents += parentheses._1
contents += subF1
contents += conn
contents += subF2
contents += parentheses._2
Vector( subF1, subF2 )
case ETOr( t1, t2 ) =>
val parentheses = connectedParentheses()
val conn = label( "∨" )
val subF1 = DrawExpansionTree( main, t1, outerQuantifier )
val subF2 = DrawExpansionTree( main, t2, outerQuantifier )
this.listenTo( conn.mouse.moves, parentheses._1.mouse.moves, parentheses._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parentheses._1.foreground = highlightColor
parentheses._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parentheses._1.foreground = Color.black
parentheses._2.foreground = Color.black
}
contents += parentheses._1
contents += subF1
contents += conn
contents += subF2
contents += parentheses._2
Vector( subF1, subF2 )
case ETImp( t1, t2 ) =>
val parentheses = connectedParentheses()
val conn = label( "→" )
val subF1 = DrawExpansionTree( main, t1, outerQuantifier )
val subF2 = DrawExpansionTree( main, t2, outerQuantifier )
this.listenTo( conn.mouse.moves, parentheses._1.mouse.moves, parentheses._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parentheses._1.foreground = highlightColor
parentheses._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parentheses._1.foreground = Color.black
parentheses._2.foreground = Color.black
}
contents += parentheses._1
contents += subF1
contents += conn
contents += subF2
contents += parentheses._2
Vector( subF1, subF2 )
case ETMerge( t1, t2 ) =>
val parentheses = connectedParentheses()
val conn = label( "⊔" )
val subF1 = DrawExpansionTree( main, t1, outerQuantifier )
val subF2 = DrawExpansionTree( main, t2, outerQuantifier )
this.listenTo( conn.mouse.moves, parentheses._1.mouse.moves, parentheses._2.mouse.moves )
this.reactions += {
case _: MouseEntered =>
conn.foreground = highlightColor
parentheses._1.foreground = highlightColor
parentheses._2.foreground = highlightColor
case _: MouseExited =>
conn.foreground = Color.black
parentheses._1.foreground = Color.black
parentheses._2.foreground = Color.black
}
contents += parentheses._1
contents += subF1
contents += conn
contents += subF2
contents += parentheses._2
Vector( subF1, subF2 )
case x =>
throw new Exception( "Could not match an ET!" ) with ExceptionTag[ExpansionTree] { val tag = x }
}
}
object ETStrongQuantifierBlock {
def unapply( et: ExpansionTree ): Some[( Formula, Int, Map[Seq[Expr], ExpansionTree] )] = et match {
case ETStrongQuantifier( _, eigen, ETStrongQuantifierBlock( _, depth, children ) ) =>
Some( ( et.shallow, depth + 1, for ( ( t, child ) <- children ) yield ( eigen +: t, child ) ) )
case ETSkolemQuantifier( _, st, ETStrongQuantifierBlock( _, depth, children ) ) =>
Some( ( et.shallow, depth + 1, for ( ( t, child ) <- children ) yield ( st +: t, child ) ) )
case _ => Some( ( et.shallow, 0, Map( Seq[Expr]() -> et ) ) )
}
}
object ETQuantifierBlock {
def unapply( et: ExpansionTree ): Option[( Formula, Int, Map[Seq[Expr], ExpansionTree] )] = et match {
case ETStrongQuantifierBlock( shallow, depth, children ) if depth > 0 => Some( ( shallow, depth, children ) )
case ETWeakQuantifierBlock( shallow, depth, children ) if depth > 0 => Some( ( shallow, depth, children ) )
case _ => None
}
}