| author | wenzelm | 
| Sun, 03 Sep 2023 16:44:07 +0200 | |
| changeset 78639 | ca56952b7322 | 
| parent 76098 | bcca0fbb8a34 | 
| child 81319 | a0b25f94c74a | 
| permissions | -rw-r--r-- | 
| 59392 | 1 | /* Title: Tools/Graphview/tree_panel.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 4 | Tree view on graph nodes. | |
| 5 | */ | |
| 6 | ||
| 7 | package isabelle.graphview | |
| 8 | ||
| 9 | ||
| 10 | import isabelle._ | |
| 11 | ||
| 59396 | 12 | import java.awt.{Dimension, Rectangle}
 | 
| 13 | import java.awt.event.{KeyEvent, KeyAdapter, MouseEvent, MouseAdapter}
 | |
| 59392 | 14 | import javax.swing.JTree | 
| 59396 | 15 | import javax.swing.tree.{DefaultMutableTreeNode, TreeSelectionModel, TreePath}
 | 
| 16 | import javax.swing.event.{TreeSelectionEvent, TreeSelectionListener, DocumentListener, DocumentEvent}
 | |
| 59392 | 17 | |
| 18 | import scala.util.matching.Regex | |
| 75852 | 19 | import scala.swing.{Component, ScrollPane, BorderPanel, Label, TextField, Button, Action}
 | 
| 59392 | 20 | |
| 21 | ||
| 75393 | 22 | class Tree_Panel(val graphview: Graphview, graph_panel: Graph_Panel) | 
| 23 | extends BorderPanel {
 | |
| 59396 | 24 | /* main actions */ | 
| 25 | ||
| 75393 | 26 |   private def selection_action(): Unit = {
 | 
| 59396 | 27 |     if (tree != null) {
 | 
| 59459 | 28 | graphview.current_node = None | 
| 29 | graphview.Selection.clear() | |
| 59396 | 30 | val paths = tree.getSelectionPaths | 
| 31 |       if (paths != null) {
 | |
| 32 |         for (path <- paths if path != null) {
 | |
| 33 |           path.getLastPathComponent match {
 | |
| 34 | case tree_node: DefaultMutableTreeNode => | |
| 35 |               tree_node.getUserObject match {
 | |
| 59459 | 36 | case node: Graph_Display.Node => graphview.Selection.add(node) | 
| 59396 | 37 | case _ => | 
| 38 | } | |
| 39 | case _ => | |
| 40 | } | |
| 41 | } | |
| 42 | } | |
| 59397 | 43 | graph_panel.repaint() | 
| 59396 | 44 | } | 
| 45 | } | |
| 46 | ||
| 75393 | 47 |   private def point_action(path: TreePath): Unit = {
 | 
| 59396 | 48 |     if (tree_pane != null && path != null) {
 | 
| 49 | val action_node = | |
| 50 |         path.getLastPathComponent match {
 | |
| 51 | case tree_node: DefaultMutableTreeNode => | |
| 52 |             tree_node.getUserObject match {
 | |
| 53 | case node: Graph_Display.Node => Some(node) | |
| 54 | case _ => None | |
| 55 | } | |
| 56 | case _ => None | |
| 57 | } | |
| 59397 | 58 | action_node.foreach(graph_panel.scroll_to_node(_)) | 
| 59459 | 59 | graphview.current_node = action_node | 
| 59397 | 60 | graph_panel.repaint() | 
| 59396 | 61 | } | 
| 62 | } | |
| 63 | ||
| 64 | ||
| 59392 | 65 | /* tree */ | 
| 66 | ||
| 67 | private var nodes = List.empty[Graph_Display.Node] | |
| 68 |   private val root = new DefaultMutableTreeNode("Nodes")
 | |
| 69 | ||
| 59396 | 70 | val tree = new JTree(root) | 
| 60292 
ba3c716144dd
cell-specific row height based on its font, e.g. relevant for DPI scaling on Windows;
 wenzelm parents: 
59459diff
changeset | 71 | tree.setRowHeight(0) | 
| 59396 | 72 | |
| 73 |   tree.addKeyListener(new KeyAdapter {
 | |
| 74 | override def keyPressed(e: KeyEvent): Unit = | |
| 75 |       if (e.getKeyCode == KeyEvent.VK_ENTER) {
 | |
| 75807 
b0394e7d43ea
tuned signature, following hints by IntelliJ IDEA;
 wenzelm parents: 
75393diff
changeset | 76 | e.consume() | 
| 59396 | 77 | selection_action() | 
| 78 | } | |
| 79 | }) | |
| 59392 | 80 |   tree.addMouseListener(new MouseAdapter {
 | 
| 59396 | 81 | override def mousePressed(e: MouseEvent): Unit = | 
| 82 | if (e.getClickCount == 2) | |
| 83 | point_action(tree.getPathForLocation(e.getX, e.getY)) | |
| 59392 | 84 | }) | 
| 85 | ||
| 86 | private val tree_pane = new ScrollPane(Component.wrap(tree)) | |
| 87 | tree_pane.horizontalScrollBarPolicy = ScrollPane.BarPolicy.Always | |
| 88 | tree_pane.verticalScrollBarPolicy = ScrollPane.BarPolicy.Always | |
| 60849 | 89 | tree_pane.minimumSize = new Dimension(200, 100) | 
| 59392 | 90 | tree_pane.peer.getVerticalScrollBar.setUnitIncrement(10) | 
| 91 | ||
| 92 | ||
| 93 | /* controls */ | |
| 94 | ||
| 95 | private var selection_pattern: Option[Regex] = None | |
| 96 | ||
| 97 | private def selection_filter(node: Graph_Display.Node): Boolean = | |
| 98 |     selection_pattern match {
 | |
| 59402 
4de91de47a6c
proper selection of nodes via regular expression;
 wenzelm parents: 
59400diff
changeset | 99 | case None => false | 
| 76098 | 100 | case Some(re) => re.findFirstIn(node.toString).isDefined | 
| 59392 | 101 | } | 
| 102 | ||
| 103 |   private val selection_label = new Label("Selection:") {
 | |
| 104 | tooltip = "Selection of nodes via regular expression" | |
| 105 | } | |
| 106 | ||
| 107 |   private val selection_field = new TextField(10) {
 | |
| 108 | tooltip = selection_label.tooltip | |
| 109 | } | |
| 110 | private val selection_field_foreground = selection_field.foreground | |
| 111 | ||
| 112 | private val selection_delay = | |
| 71704 | 113 |     Delay.last(graphview.options.seconds("editor_input_delay"), gui = true) {
 | 
| 59392 | 114 | val (pattern, ok) = | 
| 115 |         selection_field.text match {
 | |
| 116 | case null | "" => (None, true) | |
| 117 | case s => | |
| 118 | val pattern = Library.make_regex(s) | |
| 119 | (pattern, pattern.isDefined) | |
| 120 | } | |
| 121 |       if (selection_pattern != pattern) {
 | |
| 122 | selection_pattern = pattern | |
| 59402 
4de91de47a6c
proper selection of nodes via regular expression;
 wenzelm parents: 
59400diff
changeset | 123 | tree.setSelectionRows( | 
| 
4de91de47a6c
proper selection of nodes via regular expression;
 wenzelm parents: 
59400diff
changeset | 124 |           (for { (node, i) <- nodes.iterator.zipWithIndex if selection_filter(node) }
 | 
| 
4de91de47a6c
proper selection of nodes via regular expression;
 wenzelm parents: 
59400diff
changeset | 125 | yield i + 1).toArray) | 
| 
4de91de47a6c
proper selection of nodes via regular expression;
 wenzelm parents: 
59400diff
changeset | 126 | tree.repaint() | 
| 59392 | 127 | } | 
| 128 | selection_field.foreground = | |
| 59459 | 129 | if (ok) selection_field_foreground else graphview.error_color | 
| 59392 | 130 | } | 
| 131 | ||
| 73340 | 132 | selection_field.peer.getDocument.addDocumentListener( | 
| 133 |     new DocumentListener {
 | |
| 134 | def changedUpdate(e: DocumentEvent): Unit = selection_delay.invoke() | |
| 135 | def insertUpdate(e: DocumentEvent): Unit = selection_delay.invoke() | |
| 136 | def removeUpdate(e: DocumentEvent): Unit = selection_delay.invoke() | |
| 137 | }) | |
| 59392 | 138 | |
| 139 |   private val selection_apply = new Button {
 | |
| 59406 
283aa6225d98
proper tooltips -- override action toolTip which is empty here;
 wenzelm parents: 
59404diff
changeset | 140 |     action = Action("<html><b>Apply</b></html>") { selection_action () }
 | 
| 59392 | 141 | tooltip = "Apply tree selection to graph" | 
| 142 | } | |
| 143 | ||
| 59409 | 144 | private val controls = | 
| 66206 | 145 | Wrap_Panel(List(selection_label, selection_field, selection_apply)) | 
| 59392 | 146 | |
| 147 | ||
| 148 | /* main layout */ | |
| 149 | ||
| 75393 | 150 |   def refresh(): Unit = {
 | 
| 59459 | 151 | val new_nodes = graphview.visible_graph.topological_order | 
| 59392 | 152 |     if (new_nodes != nodes) {
 | 
| 60846 | 153 | tree.clearSelection | 
| 154 | ||
| 59392 | 155 | nodes = new_nodes | 
| 156 | ||
| 157 | root.removeAllChildren | |
| 158 | for (node <- nodes) root.add(new DefaultMutableTreeNode(node)) | |
| 159 | ||
| 60905 | 160 | tree.expandRow(0) | 
| 59392 | 161 | tree.revalidate() | 
| 162 | } | |
| 163 | revalidate() | |
| 164 | repaint() | |
| 165 | } | |
| 166 | ||
| 167 | layout(tree_pane) = BorderPanel.Position.Center | |
| 168 | layout(controls) = BorderPanel.Position.North | |
| 169 | refresh() | |
| 170 | } |