| author | wenzelm | 
| Sun, 15 Oct 2023 14:22:37 +0200 | |
| changeset 78782 | c44171d372a1 | 
| parent 76789 | 27a8e9e8761e | 
| child 80553 | a171f98c06a7 | 
| permissions | -rw-r--r-- | 
| 53783 
f5e9d182f645
clarified location of GUI modules (which depend on Swing of JFX);
 wenzelm parents: 
53778diff
changeset | 1 | /* Title: Pure/GUI/gui.scala | 
| 51619 | 2 | Author: Makarius | 
| 3 | ||
| 53712 | 4 | Basic GUI tools (for AWT/Swing). | 
| 51619 | 5 | */ | 
| 6 | ||
| 7 | package isabelle | |
| 8 | ||
| 73909 | 9 | import java.util.{Map => JMap}
 | 
| 73037 | 10 | import java.awt.{Component, Container, Font, Image, Insets, KeyboardFocusManager, Window, Point,
 | 
| 73117 | 11 | Rectangle, Dimension, GraphicsEnvironment, MouseInfo, Toolkit} | 
| 76494 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 12 | import java.awt.event.{KeyAdapter, KeyEvent, ItemListener, ItemEvent}
 | 
| 72974 | 13 | import java.awt.font.{FontRenderContext, LineMetrics, TextAttribute, TransformAttribute}
 | 
| 53785 | 14 | import java.awt.geom.AffineTransform | 
| 72974 | 15 | import javax.swing.{ImageIcon, JButton, JDialog, JFrame, JLabel, JLayeredPane, JOptionPane,
 | 
| 76494 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 16 | JTextField, JWindow, JComboBox, LookAndFeel, UIManager, SwingUtilities} | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 17 | |
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 18 | import scala.swing.{CheckBox, ComboBox, ScrollPane, TextArea, ListView, Label, Separator,
 | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 19 | Orientation} | 
| 75852 | 20 | import scala.swing.event.{ButtonClicked, SelectionChanged}
 | 
| 51619 | 21 | |
| 22 | ||
| 75393 | 23 | object GUI {
 | 
| 73116 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 24 | /* Swing look-and-feel */ | 
| 59201 
702e0971d617
added system property isabelle.laf, notably for initial system dialog;
 wenzelm parents: 
59183diff
changeset | 25 | |
| 73878 | 26 | def init_laf(): Unit = com.formdev.flatlaf.FlatLightLaf.setup() | 
| 51619 | 27 | |
| 73367 | 28 | def current_laf: String = UIManager.getLookAndFeel.getClass.getName() | 
| 72975 | 29 | |
| 73367 | 30 | def is_macos_laf: Boolean = | 
| 31 | Platform.is_macos && UIManager.getSystemLookAndFeelClassName() == current_laf | |
| 53848 
8d7029eb0c31
disable standard behaviour of Mac OS X text field (i.e. select-all after focus gain) in order to make completion work more smoothly;
 wenzelm parents: 
53786diff
changeset | 32 | |
| 75393 | 33 |   class Look_And_Feel(laf: LookAndFeel) extends Isabelle_System.Service {
 | 
| 73117 | 34 | def info: UIManager.LookAndFeelInfo = | 
| 35 | new UIManager.LookAndFeelInfo(laf.getName, laf.getClass.getName) | |
| 73111 | 36 | } | 
| 37 | ||
| 38 | lazy val look_and_feels: List[Look_And_Feel] = | |
| 39 | Isabelle_System.make_services(classOf[Look_And_Feel]) | |
| 40 | ||
| 75393 | 41 |   def init_lafs(): Unit = {
 | 
| 73117 | 42 | val old_lafs = | 
| 43 | Set( | |
| 44 | "com.sun.java.swing.plaf.motif.MotifLookAndFeel", | |
| 45 | "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel") | |
| 46 | val lafs = | |
| 47 | UIManager.getInstalledLookAndFeels().toList | |
| 48 | .filterNot(info => old_lafs(info.getClassName)) | |
| 49 | val more_lafs = look_and_feels.map(_.info) | |
| 50 | UIManager.setInstalledLookAndFeels((more_lafs ::: lafs).toArray) | |
| 51 | } | |
| 73111 | 52 | |
| 53 | ||
| 73116 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 54 | /* additional look-and-feels */ | 
| 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 55 | |
| 56752 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 56 | /* plain focus traversal, notably for text fields */ | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 57 | |
| 75393 | 58 |   def plain_focus_traversal(component: Component): Unit = {
 | 
| 56752 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 59 | val dummy_button = new JButton | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 60 | def apply(id: Int): Unit = | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 61 | component.setFocusTraversalKeys(id, dummy_button.getFocusTraversalKeys(id)) | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 62 | apply(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 63 | apply(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 64 | } | 
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 65 | |
| 
72b4205f4de9
uniform focus traversal via TAB / Shift-TAB for all fields, in contrast to Java defaults, but in accordance to occasional jEdit practice;
 wenzelm parents: 
56622diff
changeset | 66 | |
| 51619 | 67 | /* simple dialogs */ | 
| 68 | ||
| 75393 | 69 | def scrollable_text( | 
| 70 | raw_txt: String, | |
| 71 | width: Int = 60, | |
| 72 | height: Int = 20, | |
| 73 | editable: Boolean = false | |
| 74 |   ) : ScrollPane = {
 | |
| 59671 
9715eb8e9408
more precise position information in Isabelle/Scala, with YXML markup as in Isabelle/ML;
 wenzelm parents: 
59286diff
changeset | 75 | val txt = Output.clean_yxml(raw_txt) | 
| 51619 | 76 | val text = new TextArea(txt) | 
| 77 | if (width > 0) text.columns = width | |
| 53714 | 78 | if (height > 0 && split_lines(txt).length > height) text.rows = height | 
| 51619 | 79 | text.editable = editable | 
| 80 | new ScrollPane(text) | |
| 81 | } | |
| 82 | ||
| 75393 | 83 | private def simple_dialog( | 
| 84 | kind: Int, | |
| 85 | default_title: String, | |
| 86 | parent: Component, | |
| 87 | title: String, | |
| 88 | message: Iterable[Any] | |
| 89 |   ): Unit = {
 | |
| 57612 
990ffb84489b
clarified module name: facilitate alternative GUI frameworks;
 wenzelm parents: 
57044diff
changeset | 90 |     GUI_Thread.now {
 | 
| 65370 | 91 | val java_message = | 
| 92 |         message.iterator.map({ case x: scala.swing.Component => x.peer case x => x }).
 | |
| 93 | toArray.asInstanceOf[Array[AnyRef]] | |
| 94 | JOptionPane.showMessageDialog(parent, java_message, | |
| 51619 | 95 | if (title == null) default_title else title, kind) | 
| 96 | } | |
| 97 | } | |
| 98 | ||
| 57639 | 99 | def dialog(parent: Component, title: String, message: Any*): Unit = | 
| 51619 | 100 | simple_dialog(JOptionPane.PLAIN_MESSAGE, null, parent, title, message) | 
| 101 | ||
| 57639 | 102 | def warning_dialog(parent: Component, title: String, message: Any*): Unit = | 
| 51619 | 103 | simple_dialog(JOptionPane.WARNING_MESSAGE, "Warning", parent, title, message) | 
| 104 | ||
| 57639 | 105 | def error_dialog(parent: Component, title: String, message: Any*): Unit = | 
| 51619 | 106 | simple_dialog(JOptionPane.ERROR_MESSAGE, "Error", parent, title, message) | 
| 107 | ||
| 108 | def confirm_dialog(parent: Component, title: String, option_type: Int, message: Any*): Int = | |
| 57612 
990ffb84489b
clarified module name: facilitate alternative GUI frameworks;
 wenzelm parents: 
57044diff
changeset | 109 |     GUI_Thread.now {
 | 
| 51619 | 110 |       val java_message = message map { case x: scala.swing.Component => x.peer case x => x }
 | 
| 111 | JOptionPane.showConfirmDialog(parent, | |
| 112 | java_message.toArray.asInstanceOf[Array[AnyRef]], title, | |
| 113 | option_type, JOptionPane.QUESTION_MESSAGE) | |
| 114 | } | |
| 115 | ||
| 116 | ||
| 75852 | 117 | /* basic GUI components */ | 
| 118 | ||
| 75853 | 119 |   class Button(label: String) extends scala.swing.Button(label) {
 | 
| 120 |     def clicked(): Unit = {}
 | |
| 121 | ||
| 122 |     reactions += { case ButtonClicked(_) => clicked() }
 | |
| 123 | } | |
| 124 | ||
| 75854 | 125 |   class Check(label: String, init: Boolean = false) extends CheckBox(label) {
 | 
| 75852 | 126 |     def clicked(state: Boolean): Unit = {}
 | 
| 127 |     def clicked(): Unit = {}
 | |
| 128 | ||
| 129 | selected = init | |
| 130 |     reactions += { case ButtonClicked(_) => clicked(selected); clicked() }
 | |
| 131 | } | |
| 75839 | 132 | |
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 133 | |
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 134 | /* list selector */ | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 135 | |
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 136 |   object Selector {
 | 
| 76505 | 137 |     sealed abstract class Entry[A] { def get_value: Option[A] = Value.unapply(this) }
 | 
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 138 |     object Value {
 | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 139 | def unapply[A](entry: Entry[A]): Option[A] = | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 140 |         entry match {
 | 
| 76502 | 141 | case item: Item[_] => Some(item.value) | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 142 | case _ => None | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 143 | } | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 144 | } | 
| 76505 | 145 | def item[A](value: A): Entry[A] = Item(value, "", 0) | 
| 146 | def item_description[A](value: A, description: String): Entry[A] = Item(value, description, 0) | |
| 147 | ||
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 148 |     private case class Item[A](value: A, description: String, batch: Int) extends Entry[A] {
 | 
| 76502 | 149 | override def toString: String = proper_string(description) getOrElse value.toString | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 150 | } | 
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 151 |     private case class Separator[A](batch: Int) extends Entry[A] {
 | 
| 76503 
5944f9e70d98
clarified signature: only support nameless separator;
 wenzelm parents: 
76502diff
changeset | 152 | override def toString: String = "---" | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 153 | } | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 154 | |
| 76505 | 155 |     private def make_entries[A](batches: List[List[Entry[A]]]): List[Entry[A]] = {
 | 
| 76789 | 156 | val item_batches = batches.map(_.flatMap(Library.as_subclass(classOf[Item[A]]))) | 
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 157 | val sep_entries: List[Entry[A]] = | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 158 |         item_batches.filter(_.nonEmpty).zipWithIndex.flatMap({ case (batch, i) =>
 | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 159 | Separator[A](i) :: batch.map(_.copy(batch = i)) | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 160 | }) | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 161 | sep_entries.tail | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 162 | } | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 163 | } | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 164 | |
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 165 | class Selector[A](batches: List[Selector.Entry[A]]*) | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 166 |   extends ComboBox[Selector.Entry[A]](Selector.make_entries(batches.toList)) {
 | 
| 75839 | 167 |     def changed(): Unit = {}
 | 
| 168 | ||
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 169 | lazy val entries: List[Selector.Entry[A]] = Selector.make_entries(batches.toList) | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 170 | |
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 171 | def find_value(pred: A => Boolean): Option[Selector.Entry[A]] = | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 172 |       entries.find({ case item: Selector.Item[A] => pred(item.value) case _ => false })
 | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 173 | |
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 174 | def selection_value: Option[A] = selection.item.get_value | 
| 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 175 | |
| 76494 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 176 | override lazy val peer: JComboBox[Selector.Entry[A]] = | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 177 |       new JComboBox[Selector.Entry[A]](ComboBox.newConstantModel(entries)) with SuperMixin {
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 178 | private var key_released = false | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 179 | private var sep_selected = false | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 180 | |
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 181 |         addKeyListener(new KeyAdapter {
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 182 |           override def keyPressed(e: KeyEvent): Unit = { key_released = false }
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 183 |           override def keyReleased(e: KeyEvent): Unit = { key_released = true }
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 184 | }) | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 185 | |
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 186 |         override def setSelectedIndex(i: Int): Unit = {
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 187 |           getItemAt(i) match {
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 188 | case _: Selector.Separator[_] => | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 189 |               if (key_released) { sep_selected = true }
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 190 |               else {
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 191 | val k = getSelectedIndex() | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 192 | val j = if (i > k) i + 1 else i - 1 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 193 | if (0 <= j && j < dataModel.getSize()) super.setSelectedIndex(j) | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 194 | } | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 195 | case _ => super.setSelectedIndex(i) | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 196 | } | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 197 | } | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 198 | |
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 199 |         override def setPopupVisible(visible: Boolean): Unit = {
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 200 |           if (sep_selected) { sep_selected = false}
 | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 201 | else super.setPopupVisible(visible) | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 202 | } | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 203 | } | 
| 
9686049ce988
more robust selection: avoid duplicates via "batch" number;
 wenzelm parents: 
76492diff
changeset | 204 | |
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 205 | private val default_renderer = renderer | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 206 | private val render_separator = new Separator | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 207 | renderer = | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 208 |       (list: ListView[_ <: Selector.Entry[A]], selected: Boolean, focus: Boolean, entry: Selector.Entry[A], i: Int) => {
 | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 209 |         entry match {
 | 
| 76503 
5944f9e70d98
clarified signature: only support nameless separator;
 wenzelm parents: 
76502diff
changeset | 210 | case _: Selector.Separator[_] => render_separator | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 211 | case _ => default_renderer.componentFor(list, selected, focus, entry, i) | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 212 | } | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 213 | } | 
| 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 214 | |
| 75839 | 215 | listenTo(selection) | 
| 216 |     reactions += { case SelectionChanged(_) => changed() }
 | |
| 217 | } | |
| 51619 | 218 | |
| 75852 | 219 | |
| 220 | /* zoom factor */ | |
| 221 | ||
| 57044 | 222 | private val Zoom_Factor = "([0-9]+)%?".r | 
| 223 | ||
| 75839 | 224 | class Zoom extends Selector[String]( | 
| 75393 | 225 |     List("50%", "70%", "85%", "100%", "125%", "150%", "175%", "200%", "300%", "400%")
 | 
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 226 | .map(GUI.Selector.item) | 
| 75393 | 227 |   ) {
 | 
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 228 | def factor: Int = parse(selection.item.toString) | 
| 57044 | 229 | |
| 230 | private def parse(text: String): Int = | |
| 51619 | 231 |       text match {
 | 
| 57044 | 232 | case Zoom_Factor(s) => | 
| 51619 | 233 | val i = Integer.parseInt(s) | 
| 56874 | 234 | if (10 <= i && i < 1000) i else 100 | 
| 51619 | 235 | case _ => 100 | 
| 236 | } | |
| 237 | ||
| 57044 | 238 | private def print(i: Int): String = i.toString + "%" | 
| 51619 | 239 | |
| 75393 | 240 |     def set_item(i: Int): Unit = {
 | 
| 51619 | 241 |       peer.getEditor match {
 | 
| 242 | case null => | |
| 243 | case editor => editor.setItem(print(i)) | |
| 244 | } | |
| 245 | } | |
| 246 | ||
| 76492 
e228be7cd375
clarified GUI.Selector, with support for separator as pseudo-entry;
 wenzelm parents: 
75854diff
changeset | 247 | makeEditable()(c => | 
| 76504 
15b058bb2416
clarified signature: ensure that entries are well-formed --- no consecutive separators, no separators at start/end;
 wenzelm parents: 
76503diff
changeset | 248 | new ComboBox.BuiltInEditor(c)(text => Selector.item(print(parse(text))), _.toString)) | 
| 56874 | 249 |     peer.getEditor.getEditorComponent match {
 | 
| 56888 | 250 | case text: JTextField => text.setColumns(4) | 
| 56874 | 251 | case _ => | 
| 252 | } | |
| 253 | ||
| 57044 | 254 | selection.index = 3 | 
| 51619 | 255 | } | 
| 256 | ||
| 257 | ||
| 53786 | 258 | /* tooltip with multi-line support */ | 
| 259 | ||
| 56622 
891d1b8b64fb
clarified tooltip_lines: HTML.encode already takes care of newline (but not space);
 wenzelm parents: 
54965diff
changeset | 260 | def tooltip_lines(text: String): String = | 
| 
891d1b8b64fb
clarified tooltip_lines: HTML.encode already takes care of newline (but not space);
 wenzelm parents: 
54965diff
changeset | 261 | if (text == null || text == "") null | 
| 62113 | 262 | else "<html>" + HTML.output(text) + "</html>" | 
| 53786 | 263 | |
| 264 | ||
| 51619 | 265 | /* icon */ | 
| 266 | ||
| 267 | def isabelle_icon(): ImageIcon = | |
| 54676 
6b2ca4850b71
uniform use of transparent icons, as for main "apps";
 wenzelm parents: 
54659diff
changeset | 268 |     new ImageIcon(getClass.getClassLoader.getResource("isabelle/isabelle_transparent-32.gif"))
 | 
| 51619 | 269 | |
| 54709 | 270 | def isabelle_icons(): List[ImageIcon] = | 
| 271 |     for (icon <- List("isabelle/isabelle_transparent-32.gif", "isabelle/isabelle_transparent.gif"))
 | |
| 272 | yield new ImageIcon(getClass.getClassLoader.getResource(icon)) | |
| 273 | ||
| 51619 | 274 | def isabelle_image(): Image = isabelle_icon().getImage | 
| 53712 | 275 | |
| 276 | ||
| 72974 | 277 | /* location within multi-screen environment */ | 
| 278 | ||
| 75393 | 279 |   final case class Screen_Location(point: Point, bounds: Rectangle) {
 | 
| 280 |     def relative(parent: Component, size: Dimension): Point = {
 | |
| 72974 | 281 | val w = size.width | 
| 282 | val h = size.height | |
| 283 | ||
| 284 | val x0 = parent.getLocationOnScreen.x | |
| 285 | val y0 = parent.getLocationOnScreen.y | |
| 286 | val x1 = x0 + parent.getWidth - w | |
| 287 | val y1 = y0 + parent.getHeight - h | |
| 288 | val x2 = point.x min (bounds.x + bounds.width - w) | |
| 289 | val y2 = point.y min (bounds.y + bounds.height - h) | |
| 290 | ||
| 291 | val location = new Point((x2 min x1) max x0, (y2 min y1) max y0) | |
| 292 | SwingUtilities.convertPointFromScreen(location, parent) | |
| 293 | location | |
| 294 | } | |
| 295 | } | |
| 296 | ||
| 75393 | 297 |   def screen_location(component: Component, point: Point): Screen_Location = {
 | 
| 72974 | 298 | val screen_point = new Point(point.x, point.y) | 
| 299 | if (component != null) SwingUtilities.convertPointToScreen(screen_point, component) | |
| 300 | ||
| 301 | val ge = GraphicsEnvironment.getLocalGraphicsEnvironment | |
| 302 | val screen_bounds = | |
| 303 |       (for {
 | |
| 304 | device <- ge.getScreenDevices.iterator | |
| 305 | config <- device.getConfigurations.iterator | |
| 306 | bounds = config.getBounds | |
| 307 | } yield bounds).find(_.contains(screen_point)) getOrElse ge.getMaximumWindowBounds | |
| 308 | ||
| 309 | Screen_Location(screen_point, screen_bounds) | |
| 310 | } | |
| 311 | ||
| 312 | def mouse_location(): Screen_Location = | |
| 313 | screen_location(null, MouseInfo.getPointerInfo.getLocation) | |
| 314 | ||
| 315 | ||
| 73037 | 316 | /* screen size */ | 
| 317 | ||
| 75393 | 318 |   sealed case class Screen_Size(bounds: Rectangle, insets: Insets) {
 | 
| 73038 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 319 | def full_screen_bounds: Rectangle = | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 320 |       if (Platform.is_linux) {
 | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 321 | // avoid menu bar and docking areas | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 322 | new Rectangle( | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 323 | bounds.x + insets.left, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 324 | bounds.y + insets.top, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 325 | bounds.width - insets.left - insets.right, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 326 | bounds.height - insets.top - insets.bottom) | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 327 | } | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 328 |       else if (Platform.is_macos) {
 | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 329 | // avoid menu bar, but ignore docking areas | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 330 | new Rectangle( | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 331 | bounds.x, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 332 | bounds.y + insets.top, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 333 | bounds.width, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 334 | bounds.height - insets.top) | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 335 | } | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 336 | else bounds | 
| 73037 | 337 | } | 
| 338 | ||
| 75393 | 339 |   def screen_size(component: Component): Screen_Size = {
 | 
| 73037 | 340 | val config = component.getGraphicsConfiguration | 
| 341 | val bounds = config.getBounds | |
| 342 | val insets = Toolkit.getDefaultToolkit.getScreenInsets(config) | |
| 343 | Screen_Size(bounds, insets) | |
| 344 | } | |
| 345 | ||
| 346 | ||
| 53712 | 347 | /* component hierachy */ | 
| 348 | ||
| 349 | def get_parent(component: Component): Option[Container] = | |
| 350 |     component.getParent match {
 | |
| 351 | case null => None | |
| 352 | case parent => Some(parent) | |
| 353 | } | |
| 354 | ||
| 355 |   def ancestors(component: Component): Iterator[Container] = new Iterator[Container] {
 | |
| 356 | private var next_elem = get_parent(component) | |
| 73337 | 357 | def hasNext: Boolean = next_elem.isDefined | 
| 53712 | 358 | def next(): Container = | 
| 359 |       next_elem match {
 | |
| 360 | case Some(parent) => | |
| 361 | next_elem = get_parent(parent) | |
| 362 | parent | |
| 363 | case None => Iterator.empty.next() | |
| 364 | } | |
| 365 | } | |
| 366 | ||
| 367 | def parent_window(component: Component): Option[Window] = | |
| 368 |     ancestors(component).collectFirst({ case x: Window => x })
 | |
| 53778 
29eaacff4078
proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
 wenzelm parents: 
53714diff
changeset | 369 | |
| 
29eaacff4078
proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
 wenzelm parents: 
53714diff
changeset | 370 | def layered_pane(component: Component): Option[JLayeredPane] = | 
| 
29eaacff4078
proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
 wenzelm parents: 
53714diff
changeset | 371 |     parent_window(component) match {
 | 
| 57879 
91e188508bc9
proper layered_pane for JDialog, e.g. relevant for floating dockables in jEdit, for completion popup in text field;
 wenzelm parents: 
57639diff
changeset | 372 | case Some(w: JWindow) => Some(w.getLayeredPane) | 
| 
91e188508bc9
proper layered_pane for JDialog, e.g. relevant for floating dockables in jEdit, for completion popup in text field;
 wenzelm parents: 
57639diff
changeset | 373 | case Some(w: JFrame) => Some(w.getLayeredPane) | 
| 
91e188508bc9
proper layered_pane for JDialog, e.g. relevant for floating dockables in jEdit, for completion popup in text field;
 wenzelm parents: 
57639diff
changeset | 374 | case Some(w: JDialog) => Some(w.getLayeredPane) | 
| 53778 
29eaacff4078
proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
 wenzelm parents: 
53714diff
changeset | 375 | case _ => None | 
| 
29eaacff4078
proper layered pane at root of parent component, not global view (e.g. relevant for tooltips for detached info windows);
 wenzelm parents: 
53714diff
changeset | 376 | } | 
| 53785 | 377 | |
| 75393 | 378 |   def traverse_components(component: Component, apply: Component => Unit): Unit = {
 | 
| 379 |     def traverse(comp: Component): Unit = {
 | |
| 63874 | 380 | apply(comp) | 
| 381 |       comp match {
 | |
| 382 | case cont: Container => | |
| 383 | for (i <- 0 until cont.getComponentCount) | |
| 384 | traverse(cont.getComponent(i)) | |
| 385 | case _ => | |
| 386 | } | |
| 387 | } | |
| 388 | traverse(component) | |
| 389 | } | |
| 390 | ||
| 53785 | 391 | |
| 392 | /* font operations */ | |
| 393 | ||
| 61742 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 394 | def copy_font(font: Font): Font = | 
| 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 395 | if (font == null) null | 
| 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 396 | else new Font(font.getFamily, font.getStyle, font.getSize) | 
| 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 397 | |
| 59230 | 398 | def line_metrics(font: Font): LineMetrics = | 
| 53785 | 399 |     font.getLineMetrics("", new FontRenderContext(null, false, false))
 | 
| 400 | ||
| 69376 | 401 | def transform_font(font: Font, transform: AffineTransform): Font = | 
| 73909 | 402 | font.deriveFont(JMap.of(TextAttribute.TRANSFORM, new TransformAttribute(transform))) | 
| 69376 | 403 | |
| 404 | def font(family: String = Isabelle_Fonts.sans, size: Int = 1, bold: Boolean = false): Font = | |
| 405 | new Font(family, if (bold) Font.BOLD else Font.PLAIN, size) | |
| 406 | ||
| 69377 | 407 | def label_font(): Font = (new JLabel).getFont | 
| 408 | ||
| 69376 | 409 | |
| 410 | /* Isabelle fonts */ | |
| 411 | ||
| 75393 | 412 | def imitate_font( | 
| 413 | font: Font, | |
| 69358 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 414 | family: String = Isabelle_Fonts.sans, | 
| 75393 | 415 | scale: Double = 1.0 | 
| 416 |   ): Font = {
 | |
| 53785 | 417 | val font1 = new Font(family, font.getStyle, font.getSize) | 
| 59286 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59230diff
changeset | 418 | val rel_size = line_metrics(font).getHeight.toDouble / line_metrics(font1).getHeight | 
| 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59230diff
changeset | 419 | new Font(family, font.getStyle, (scale * rel_size * font.getSize).toInt) | 
| 59183 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 420 | } | 
| 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 421 | |
| 75393 | 422 | def imitate_font_css( | 
| 423 | font: Font, | |
| 69358 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 424 | family: String = Isabelle_Fonts.sans, | 
| 75393 | 425 | scale: Double = 1.0 | 
| 426 |   ): String = {
 | |
| 59183 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 427 | val font1 = new Font(family, font.getStyle, font.getSize) | 
| 59230 | 428 | val rel_size = line_metrics(font).getHeight.toDouble / line_metrics(font1).getHeight | 
| 59183 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 429 | "font-family: " + family + "; font-size: " + (scale * rel_size * 100).toInt + "%;" | 
| 53785 | 430 | } | 
| 69377 | 431 | |
| 75393 | 432 |   def use_isabelle_fonts(): Unit = {
 | 
| 69377 | 433 | val default_font = label_font() | 
| 434 | val ui = UIManager.getDefaults | |
| 75393 | 435 | for (prop <- | 
| 436 | List( | |
| 437 | "ToggleButton.font", | |
| 438 | "CheckBoxMenuItem.font", | |
| 439 | "Label.font", | |
| 440 | "Menu.font", | |
| 441 | "MenuItem.font", | |
| 442 | "PopupMenu.font", | |
| 443 | "Table.font", | |
| 444 | "TableHeader.font", | |
| 445 | "TextArea.font", | |
| 446 | "TextField.font", | |
| 447 | "TextPane.font", | |
| 448 | "ToolTip.font", | |
| 449 |         "Tree.font")) {
 | |
| 69377 | 450 |       val font = ui.get(prop) match { case font: Font => font case _ => default_font }
 | 
| 451 | ui.put(prop, GUI.imitate_font(font)) | |
| 452 | } | |
| 453 | } | |
| 51619 | 454 | } | 
| 73111 | 455 | |
| 456 | class FlatLightLaf extends GUI.Look_And_Feel(new com.formdev.flatlaf.FlatLightLaf) | |
| 457 | class FlatDarkLaf extends GUI.Look_And_Feel(new com.formdev.flatlaf.FlatDarkLaf) |