src/Tools/jEdit/src/timing_dockable.scala
author wenzelm
Thu, 04 Mar 2021 15:41:46 +0100
changeset 73359 d8a0e996614b
parent 73358 78aa7846e91f
child 73361 ef8c9b3d5355
permissions -rw-r--r--
tuned --- fewer warnings;

/*  Title:      Tools/jEdit/src/timing_dockable.scala
    Author:     Makarius

Dockable window for timing information.
*/

package isabelle.jedit


import isabelle._
import isabelle.jedit_base.Dockable

import scala.swing.{Label, ListView, Alignment, ScrollPane, Component, TextField}
import scala.swing.event.{MouseClicked, ValueChanged}

import java.awt.{BorderLayout, Graphics2D, Insets, Color}
import javax.swing.{JList, BorderFactory}
import javax.swing.border.{BevelBorder, SoftBevelBorder}

import org.gjt.sp.jedit.{View, jEdit}


class Timing_Dockable(view: View, position: String) extends Dockable(view, position)
{
  /* entry */

  private object Entry
  {
    object Ordering extends scala.math.Ordering[Entry]
    {
      def compare(entry1: Entry, entry2: Entry): Int =
        entry2.timing compare entry1.timing
    }

    object Renderer_Component extends Label
    {
      opaque = false
      xAlignment = Alignment.Leading
      border = BorderFactory.createEmptyBorder(2, 2, 2, 2)

      var entry: Entry = null
      override def paintComponent(gfx: Graphics2D): Unit =
      {
        def paint_rectangle(color: Color): Unit =
        {
          val size = peer.getSize()
          val insets = border.getBorderInsets(peer)
          val x = insets.left
          val y = insets.top
          val w = size.width - x - insets.right
          val h = size.height - y - insets.bottom
          gfx.setColor(color)
          gfx.fillRect(x, y, w, h)
        }

        entry match {
          case theory_entry: Theory_Entry if theory_entry.current =>
            paint_rectangle(view.getTextArea.getPainter.getSelectionColor)
          case _: Command_Entry =>
            paint_rectangle(view.getTextArea.getPainter.getMultipleSelectionColor)
          case _ =>
        }
        super.paintComponent(gfx)
      }
    }

    class Renderer extends ListView.Renderer[Entry]
    {
      def componentFor(list: ListView[_ <: Timing_Dockable.this.Entry],
        isSelected: Boolean, focused: Boolean, entry: Entry, index: Int): Component =
      {
        val component = Renderer_Component
        component.entry = entry
        component.text = entry.print
        component
      }
    }
  }

  private abstract class Entry
  {
    def timing: Double
    def print: String
    def follow(snapshot: Document.Snapshot): Unit
  }

  private case class Theory_Entry(name: Document.Node.Name, timing: Double, current: Boolean)
    extends Entry
  {
    def print: String =
      Time.print_seconds(timing) + "s theory " + quote(name.theory)
    def follow(snapshot: Document.Snapshot): Unit =
      PIDE.editor.goto_file(true, view, name.node)
  }

  private case class Command_Entry(command: Command, timing: Double) extends Entry
  {
    def print: String =
      "  " + Time.print_seconds(timing) + "s command " + quote(command.span.name)
    def follow(snapshot: Document.Snapshot): Unit =
      PIDE.editor.hyperlink_command(true, snapshot, command.id).foreach(_.follow(view))
  }


  /* timing view */

  private val timing_view = new ListView(Nil: List[Entry]) {
    listenTo(mouse.clicks)
    reactions += {
      case MouseClicked(_, point, _, clicks, _) if clicks == 2 =>
        val index = peer.locationToIndex(point)
        if (index >= 0) listData(index).follow(PIDE.session.snapshot())
    }
  }
  timing_view.peer.setLayoutOrientation(JList.VERTICAL_WRAP)
  timing_view.peer.setVisibleRowCount(0)
  timing_view.selection.intervalMode = ListView.IntervalMode.Single
  timing_view.renderer = new Entry.Renderer

  set_content(new ScrollPane(timing_view))


  /* timing threshold */

  private var timing_threshold = PIDE.options.real("jedit_timing_threshold")

  private val threshold_tooltip = "Threshold for timing display (seconds)"

  private val threshold_label = new Label("Threshold: ") {
    tooltip = threshold_tooltip
  }

  private val threshold_value = new TextField(Time.print_seconds(timing_threshold)) {
    reactions += {
      case _: ValueChanged =>
        text match {
          case Value.Double(x) if x >= 0.0 => timing_threshold = x
          case _ =>
        }
        handle_update()
    }
    tooltip = threshold_tooltip
    verifier = ((s: String) =>
      s match { case Value.Double(x) => x >= 0.0 case _ => false })
  }

  private val controls = Wrap_Panel(List(threshold_label, threshold_value))

  add(controls.peer, BorderLayout.NORTH)


  /* component state -- owned by GUI thread */

  private var nodes_timing = Map.empty[Document.Node.Name, Document_Status.Overall_Timing]

  private def make_entries(): List[Entry] =
  {
    GUI_Thread.require {}

    val name =
      Document_View.get(view.getTextArea) match {
        case None => Document.Node.Name.empty
        case Some(doc_view) => doc_view.model.node_name
      }
    val timing = nodes_timing.getOrElse(name, Document_Status.Overall_Timing.empty)

    val theories =
      (for ((node_name, node_timing) <- nodes_timing.toList if node_timing.command_timings.nonEmpty)
        yield Theory_Entry(node_name, node_timing.total, false)).sorted(Entry.Ordering)
    val commands =
      (for ((command, command_timing) <- timing.command_timings.toList)
        yield Command_Entry(command, command_timing)).sorted(Entry.Ordering)

    theories.flatMap(entry =>
      if (entry.name == name) entry.copy(current = true) :: commands
      else List(entry))
  }

  private def handle_update(restriction: Option[Set[Document.Node.Name]] = None): Unit =
  {
    GUI_Thread.require {}

    val snapshot = PIDE.session.snapshot()

    val nodes_timing1 =
      (restriction match {
        case Some(names) => names.iterator.map(name => (name, snapshot.get_node(name)))
        case None => snapshot.version.nodes.iterator
      }).foldLeft(nodes_timing) {
          case (timing1, (name, node)) =>
            if (PIDE.resources.session_base.loaded_theory(name)) timing1
            else {
              val node_timing =
                Document_Status.Overall_Timing.make(
                  snapshot.state, snapshot.version, node.commands, threshold = timing_threshold)
              timing1 + (name -> node_timing)
            }
        }
    nodes_timing = nodes_timing1

    val entries = make_entries()
    if (timing_view.listData.toList != entries) timing_view.listData = entries
  }


  /* main */

  private val main =
    Session.Consumer[Session.Commands_Changed](getClass.getName) {
      case changed =>
        GUI_Thread.later { handle_update(Some(changed.nodes)) }
    }

  override def init(): Unit =
  {
    PIDE.session.commands_changed += main
    handle_update()
  }

  override def exit(): Unit =
  {
    PIDE.session.commands_changed -= main
  }
}