src/Tools/Graphview/mutator_dialog.scala
author wenzelm
Mon, 01 Mar 2021 22:22:12 +0100
changeset 73340 0ffcad1f6130
parent 71601 97ccf48c2f0c
child 73344 f5c147654661
permissions -rw-r--r--
tuned --- fewer warnings;

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

Mutator selection and configuration dialog.
*/

package isabelle.graphview


import isabelle._

import java.awt.Color
import java.awt.FocusTraversalPolicy
import javax.swing.JColorChooser
import javax.swing.border.EmptyBorder
import scala.swing.{Dialog, Button, BoxPanel, Swing, Orientation, ComboBox, Action,
  Dimension, BorderPanel, ScrollPane, Label, CheckBox, Alignment, Component, TextField}
import scala.swing.event.ValueChanged


class Mutator_Dialog(
    graphview: Graphview,
    container: Mutator_Container,
    caption: String,
    reverse_caption: String = "Inverse",
    show_color_chooser: Boolean = true)
  extends Dialog
{
  title = caption

  private var _panels: List[Mutator_Panel] = Nil
  private def panels = _panels
  private def panels_=(panels: List[Mutator_Panel]): Unit =
  {
    _panels = panels
    paint_panels()
  }

  container.events +=
  {
    case Mutator_Event.Add(m) => add_panel(new Mutator_Panel(m))
    case Mutator_Event.New_List(ms) => panels = get_panels(ms)
  }

  override def open(): Unit =
  {
    if (!visible) panels = get_panels(container())
    super.open
  }

  minimumSize = new Dimension(700, 200)
  preferredSize = new Dimension(1000, 300)
  peer.setFocusTraversalPolicy(Focus_Traveral_Policy)

  private def get_panels(m: List[Mutator.Info]): List[Mutator_Panel] =
    m.filter({ case Mutator.Info(_, _, Mutator.Identity()) => false case _ => true })
    .map(m => new Mutator_Panel(m))

  private def get_mutators(panels: List[Mutator_Panel]): List[Mutator.Info] =
    panels.map(panel => panel.get_mutator)

  private def movePanelUp(m: Mutator_Panel) =
  {
    def moveUp(l: List[Mutator_Panel]): List[Mutator_Panel] =
      l match {
        case x :: y :: xs => if (y == m) y :: x :: xs else x :: moveUp(y :: xs)
        case _ => l
      }

    panels = moveUp(panels)
  }

  private def movePanelDown(m: Mutator_Panel) =
  {
    def moveDown(l: List[Mutator_Panel]): List[Mutator_Panel] =
      l match {
        case x :: y :: xs => if (x == m) y :: x :: xs else x :: moveDown(y :: xs)
        case _ => l
      }

    panels = moveDown(panels)
  }

  private def removePanel(m: Mutator_Panel): Unit =
  {
    panels = panels.filter(_ != m).toList
  }

  private def add_panel(m: Mutator_Panel): Unit =
  {
    panels = panels ::: List(m)
  }

  def paint_panels(): Unit =
  {
    Focus_Traveral_Policy.clear
    filter_panel.contents.clear
    panels.map(x => {
        filter_panel.contents += x
        Focus_Traveral_Policy.addAll(x.focusList)
      })
    filter_panel.contents += Swing.VGlue

    Focus_Traveral_Policy.add(mutator_box.peer.asInstanceOf[java.awt.Component])
    Focus_Traveral_Policy.add(add_button.peer)
    Focus_Traveral_Policy.add(apply_button.peer)
    Focus_Traveral_Policy.add(cancel_button.peer)
    filter_panel.revalidate
    filter_panel.repaint
  }

  val filter_panel: BoxPanel = new BoxPanel(Orientation.Vertical) {}

  private val mutator_box = new ComboBox[Mutator](container.available)

  private val add_button = new Button {
    action = Action("Add") {
      add_panel(
        new Mutator_Panel(Mutator.Info(true, graphview.Colors.next, mutator_box.selection.item)))
    }
  }

  private val apply_button = new Button {
    action = Action("Apply") { container(get_mutators(panels)) }
  }

  private val reset_button = new Button {
    action = Action("Reset") { panels = get_panels(container()) }
  }

  private val cancel_button = new Button {
    action = Action("Close") { close }
  }
  defaultButton = cancel_button

  private val botPanel = new BoxPanel(Orientation.Horizontal) {
    border = new EmptyBorder(10, 0, 0, 0)

    contents += mutator_box
    contents += Swing.RigidBox(new Dimension(10, 0))
    contents += add_button

    contents += Swing.HGlue
    contents += Swing.RigidBox(new Dimension(30, 0))
    contents += apply_button

    contents += Swing.RigidBox(new Dimension(5, 0))
    contents += reset_button

    contents += Swing.RigidBox(new Dimension(5, 0))
    contents += cancel_button
  }

  contents = new BorderPanel {
    border = new EmptyBorder(5, 5, 5, 5)

    layout(new ScrollPane(filter_panel)) = BorderPanel.Position.Center
    layout(botPanel) = BorderPanel.Position.South
  }

  private class Mutator_Panel(initials: Mutator.Info)
    extends BoxPanel(Orientation.Horizontal)
  {
    private val inputs: List[(String, Input)] = get_inputs()
    var focusList = List.empty[java.awt.Component]
    private val enabledBox = new Check_Box_Input("Enabled", initials.enabled)

    border = new EmptyBorder(5, 5, 5, 5)
    maximumSize = new Dimension(Integer.MAX_VALUE, 30)
    background = initials.color

    contents += new Label(initials.mutator.name) {
      preferredSize = new Dimension(175, 20)
      horizontalAlignment = Alignment.Left
      if (initials.mutator.description != "") tooltip = initials.mutator.description
    }
    contents += Swing.RigidBox(new Dimension(10, 0))
    contents += enabledBox
    contents += Swing.RigidBox(new Dimension(5, 0))
    focusList = enabledBox.peer :: focusList
    inputs.map({
      case (n, c) =>
        contents += Swing.RigidBox(new Dimension(10, 0))
        if (n != "") {
          contents += new Label(n)
          contents += Swing.RigidBox(new Dimension(5, 0))
        }
        contents += c.asInstanceOf[Component]

        focusList = c.asInstanceOf[Component].peer :: focusList
    })

    {
      val t = this
      contents += Swing.HGlue
      contents += Swing.RigidBox(new Dimension(10, 0))

      if (show_color_chooser) {
        contents += new Button {
          maximumSize = new Dimension(20, 20)

          action = Action("Color") {
            t.background =
              JColorChooser.showDialog(t.peer, "Choose new Color", t.background)
          }

          focusList = this.peer :: focusList
        }
        contents += Swing.RigidBox(new Dimension(2, 0))
      }

      contents += new Button {
        maximumSize = new Dimension(20, 20)

        action = Action("Up") {
          movePanelUp(t)
        }

        focusList = this.peer :: focusList
      }
      contents += Swing.RigidBox(new Dimension(2, 0))
      contents += new Button {
        maximumSize = new Dimension(20, 20)

        action = Action("Down") {
          movePanelDown(t)
        }

        focusList = this.peer :: focusList
      }
      contents += Swing.RigidBox(new Dimension(2, 0))
      contents += new Button {
        maximumSize = new Dimension(20, 20)

        action = Action("Del") {
          removePanel(t)
        }

        focusList = this.peer :: focusList
      }
    }

    focusList = focusList.reverse

    def get_mutator: Mutator.Info =
    {
      val model = graphview.model
      val m =
        initials.mutator match {
          case Mutator.Identity() =>
            Mutator.Identity()
          case Mutator.Node_Expression(r, _, _, _) =>
            val r1 = inputs(2)._2.string
            Mutator.Node_Expression(
              if (Library.make_regex(r1).isDefined) r1 else r,
              inputs(3)._2.bool,
              // "Parents" means "Show parents" or "Matching Children"
              inputs(1)._2.bool,
              inputs(0)._2.bool)
          case Mutator.Node_List(_, _, _, _) =>
            Mutator.Node_List(
              for {
                ident <- space_explode(',', inputs(2)._2.string)
                node <- model.find_node(ident)
              } yield node,
              inputs(3)._2.bool,
              // "Parents" means "Show parents" or "Matching Children"
              inputs(1)._2.bool,
              inputs(0)._2.bool)
          case Mutator.Edge_Endpoints(_) =>
            (model.find_node(inputs(0)._2.string), model.find_node(inputs(1)._2.string)) match {
              case (Some(node1), Some(node2)) =>
                Mutator.Edge_Endpoints((node1, node2))
              case _ =>
                Mutator.Identity()
            }
          case Mutator.Add_Node_Expression(r) =>
            val r1 = inputs(0)._2.string
            Mutator.Add_Node_Expression(if (Library.make_regex(r1).isDefined) r1 else r)
          case Mutator.Add_Transitive_Closure(_, _) =>
            Mutator.Add_Transitive_Closure(
              inputs(0)._2.bool,
              inputs(1)._2.bool)
          case _ =>
            Mutator.Identity()
        }

      Mutator.Info(enabledBox.selected, background, m)
    }

    private def get_inputs(): List[(String, Input)] =
      initials.mutator match {
        case Mutator.Node_Expression(regex, reverse, check_parents, check_children) =>
          List(
            ("", new Check_Box_Input("Parents", check_children)),
            ("", new Check_Box_Input("Children", check_parents)),
            ("Regex", new Text_Field_Input(regex, x => Library.make_regex(x).isDefined)),
            ("", new Check_Box_Input(reverse_caption, reverse)))
        case Mutator.Node_List(list, reverse, check_parents, check_children) =>
          List(
            ("", new Check_Box_Input("Parents", check_children)),
            ("", new Check_Box_Input("Children", check_parents)),
            ("Names", new Text_Field_Input(list.map(_.ident).mkString(","))),
            ("", new Check_Box_Input(reverse_caption, reverse)))
        case Mutator.Edge_Endpoints((source, dest)) =>
          List(
            ("Source", new Text_Field_Input(source.ident)),
            ("Destination", new Text_Field_Input(dest.ident)))
        case Mutator.Add_Node_Expression(regex) =>
          List(("Regex", new Text_Field_Input(regex, x => Library.make_regex(x).isDefined)))
        case Mutator.Add_Transitive_Closure(parents, children) =>
          List(
            ("", new Check_Box_Input("Parents", parents)),
            ("", new Check_Box_Input("Children", children)))
        case _ => Nil
      }
  }

  private trait Input
  {
    def string: String
    def bool: Boolean
  }

  private class Text_Field_Input(txt: String, check: String => Boolean = (_: String) => true)
    extends TextField(txt) with Input
  {
    preferredSize = new Dimension(125, 18)

    private val default_foreground = foreground
    reactions +=
    {
      case ValueChanged(_) =>
        foreground = if (check(text)) default_foreground else graphview.error_color
    }

    def string = text
    def bool = true
  }

  private class Check_Box_Input(txt: String, s: Boolean) extends CheckBox(txt) with Input
  {
    selected = s

    def string = ""
    def bool = selected
  }

  private object Focus_Traveral_Policy extends FocusTraversalPolicy
  {
    private var items = Vector.empty[java.awt.Component]

    def add(c: java.awt.Component): Unit = { items = items :+ c }
    def addAll(cs: TraversableOnce[java.awt.Component]): Unit = { items = items ++ cs }
    def clear(): Unit = { items = Vector.empty }

    def getComponentAfter(root: java.awt.Container, c: java.awt.Component): java.awt.Component =
    {
      val i = items.indexOf(c)
      if (i < 0) getDefaultComponent(root)
      else items((i + 1) % items.length)
    }

    def getComponentBefore(root: java.awt.Container, c: java.awt.Component): java.awt.Component =
    {
      val i = items.indexOf(c)
      if (i < 0) getDefaultComponent(root)
      else items((i - 1) % items.length)
    }

    def getFirstComponent(root: java.awt.Container): java.awt.Component =
      if (items.nonEmpty) items(0) else null

    def getDefaultComponent(root: java.awt.Container): java.awt.Component =
      getFirstComponent(root)

    def getLastComponent(root: java.awt.Container): java.awt.Component =
      if (items.nonEmpty) items.last else null
  }
}