| author | wenzelm | 
| Fri, 12 Mar 2021 19:42:18 +0100 | |
| changeset 73414 | 7411d71b9fb8 | 
| parent 73367 | 77ef8bef0593 | 
| child 73878 | 291597140695 | 
| 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 | ||
| 73037 | 9 | import java.awt.{Component, Container, Font, Image, Insets, KeyboardFocusManager, Window, Point,
 | 
| 73117 | 10 | Rectangle, Dimension, GraphicsEnvironment, MouseInfo, Toolkit} | 
| 72974 | 11 | import java.awt.font.{FontRenderContext, LineMetrics, TextAttribute, TransformAttribute}
 | 
| 53785 | 12 | import java.awt.geom.AffineTransform | 
| 72974 | 13 | import javax.swing.{ImageIcon, JButton, JDialog, JFrame, JLabel, JLayeredPane, JOptionPane,
 | 
| 14 | JTextField, JWindow, LookAndFeel, UIManager, SwingUtilities} | |
| 15 | import scala.swing.{ComboBox, ScrollPane, TextArea}
 | |
| 51619 | 16 | import scala.swing.event.SelectionChanged | 
| 17 | ||
| 18 | ||
| 19 | object GUI | |
| 20 | {
 | |
| 73116 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 21 | /* Swing look-and-feel */ | 
| 59201 
702e0971d617
added system property isabelle.laf, notably for initial system dialog;
 wenzelm parents: 
59183diff
changeset | 22 | |
| 73116 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 23 | def init_laf(): Unit = com.formdev.flatlaf.FlatLightLaf.install() | 
| 51619 | 24 | |
| 73367 | 25 | def current_laf: String = UIManager.getLookAndFeel.getClass.getName() | 
| 72975 | 26 | |
| 73367 | 27 | def is_macos_laf: Boolean = | 
| 28 | 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 | 29 | |
| 73117 | 30 | class Look_And_Feel(laf: LookAndFeel) extends Isabelle_System.Service | 
| 73111 | 31 |   {
 | 
| 73117 | 32 | def info: UIManager.LookAndFeelInfo = | 
| 33 | new UIManager.LookAndFeelInfo(laf.getName, laf.getClass.getName) | |
| 73111 | 34 | } | 
| 35 | ||
| 36 | lazy val look_and_feels: List[Look_And_Feel] = | |
| 37 | Isabelle_System.make_services(classOf[Look_And_Feel]) | |
| 38 | ||
| 73340 | 39 | def init_lafs(): Unit = | 
| 73117 | 40 |   {
 | 
| 41 | val old_lafs = | |
| 42 | Set( | |
| 43 | "com.sun.java.swing.plaf.motif.MotifLookAndFeel", | |
| 44 | "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel") | |
| 45 | val lafs = | |
| 46 | UIManager.getInstalledLookAndFeels().toList | |
| 47 | .filterNot(info => old_lafs(info.getClassName)) | |
| 48 | val more_lafs = look_and_feels.map(_.info) | |
| 49 | UIManager.setInstalledLookAndFeels((more_lafs ::: lafs).toArray) | |
| 50 | } | |
| 73111 | 51 | |
| 52 | ||
| 73116 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 53 | /* additional look-and-feels */ | 
| 
b84887a67cc6
clarified default L&F of Isabelle/Scala (not Isabelle/jEdit);
 wenzelm parents: 
73111diff
changeset | 54 | |
| 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 | 55 | /* 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 | 56 | |
| 73340 | 57 | 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 | 58 |   {
 | 
| 
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 | ||
| 59671 
9715eb8e9408
more precise position information in Isabelle/Scala, with YXML markup as in Isabelle/ML;
 wenzelm parents: 
59286diff
changeset | 69 | def scrollable_text(raw_txt: String, width: Int = 60, height: Int = 20, editable: Boolean = false) | 
| 53714 | 70 | : ScrollPane = | 
| 51619 | 71 |   {
 | 
| 59671 
9715eb8e9408
more precise position information in Isabelle/Scala, with YXML markup as in Isabelle/ML;
 wenzelm parents: 
59286diff
changeset | 72 | val txt = Output.clean_yxml(raw_txt) | 
| 51619 | 73 | val text = new TextArea(txt) | 
| 74 | if (width > 0) text.columns = width | |
| 53714 | 75 | if (height > 0 && split_lines(txt).length > height) text.rows = height | 
| 51619 | 76 | text.editable = editable | 
| 77 | new ScrollPane(text) | |
| 78 | } | |
| 79 | ||
| 80 | private def simple_dialog(kind: Int, default_title: String, | |
| 73340 | 81 | parent: Component, title: String, message: Iterable[Any]): Unit = | 
| 51619 | 82 |   {
 | 
| 57612 
990ffb84489b
clarified module name: facilitate alternative GUI frameworks;
 wenzelm parents: 
57044diff
changeset | 83 |     GUI_Thread.now {
 | 
| 65370 | 84 | val java_message = | 
| 85 |         message.iterator.map({ case x: scala.swing.Component => x.peer case x => x }).
 | |
| 86 | toArray.asInstanceOf[Array[AnyRef]] | |
| 87 | JOptionPane.showMessageDialog(parent, java_message, | |
| 51619 | 88 | if (title == null) default_title else title, kind) | 
| 89 | } | |
| 90 | } | |
| 91 | ||
| 57639 | 92 | def dialog(parent: Component, title: String, message: Any*): Unit = | 
| 51619 | 93 | simple_dialog(JOptionPane.PLAIN_MESSAGE, null, parent, title, message) | 
| 94 | ||
| 57639 | 95 | def warning_dialog(parent: Component, title: String, message: Any*): Unit = | 
| 51619 | 96 | simple_dialog(JOptionPane.WARNING_MESSAGE, "Warning", parent, title, message) | 
| 97 | ||
| 57639 | 98 | def error_dialog(parent: Component, title: String, message: Any*): Unit = | 
| 51619 | 99 | simple_dialog(JOptionPane.ERROR_MESSAGE, "Error", parent, title, message) | 
| 100 | ||
| 101 | 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 | 102 |     GUI_Thread.now {
 | 
| 51619 | 103 |       val java_message = message map { case x: scala.swing.Component => x.peer case x => x }
 | 
| 104 | JOptionPane.showConfirmDialog(parent, | |
| 105 | java_message.toArray.asInstanceOf[Array[AnyRef]], title, | |
| 106 | option_type, JOptionPane.QUESTION_MESSAGE) | |
| 107 | } | |
| 108 | ||
| 109 | ||
| 110 | /* zoom box */ | |
| 111 | ||
| 57044 | 112 | private val Zoom_Factor = "([0-9]+)%?".r | 
| 113 | ||
| 114 | abstract class Zoom_Box extends ComboBox[String]( | |
| 51619 | 115 |     List("50%", "70%", "85%", "100%", "125%", "150%", "175%", "200%", "300%", "400%"))
 | 
| 116 |   {
 | |
| 57044 | 117 | def changed: Unit | 
| 118 | def factor: Int = parse(selection.item) | |
| 119 | ||
| 120 | private def parse(text: String): Int = | |
| 51619 | 121 |       text match {
 | 
| 57044 | 122 | case Zoom_Factor(s) => | 
| 51619 | 123 | val i = Integer.parseInt(s) | 
| 56874 | 124 | if (10 <= i && i < 1000) i else 100 | 
| 51619 | 125 | case _ => 100 | 
| 126 | } | |
| 127 | ||
| 57044 | 128 | private def print(i: Int): String = i.toString + "%" | 
| 51619 | 129 | |
| 73340 | 130 | def set_item(i: Int): Unit = | 
| 131 |     {
 | |
| 51619 | 132 |       peer.getEditor match {
 | 
| 133 | case null => | |
| 134 | case editor => editor.setItem(print(i)) | |
| 135 | } | |
| 136 | } | |
| 137 | ||
| 138 | makeEditable()(c => new ComboBox.BuiltInEditor(c)(text => print(parse(text)), x => x)) | |
| 56874 | 139 |     peer.getEditor.getEditorComponent match {
 | 
| 56888 | 140 | case text: JTextField => text.setColumns(4) | 
| 56874 | 141 | case _ => | 
| 142 | } | |
| 143 | ||
| 57044 | 144 | selection.index = 3 | 
| 145 | ||
| 51619 | 146 | listenTo(selection) | 
| 57044 | 147 |     reactions += { case SelectionChanged(_) => changed }
 | 
| 51619 | 148 | } | 
| 149 | ||
| 150 | ||
| 53786 | 151 | /* tooltip with multi-line support */ | 
| 152 | ||
| 56622 
891d1b8b64fb
clarified tooltip_lines: HTML.encode already takes care of newline (but not space);
 wenzelm parents: 
54965diff
changeset | 153 | def tooltip_lines(text: String): String = | 
| 
891d1b8b64fb
clarified tooltip_lines: HTML.encode already takes care of newline (but not space);
 wenzelm parents: 
54965diff
changeset | 154 | if (text == null || text == "") null | 
| 62113 | 155 | else "<html>" + HTML.output(text) + "</html>" | 
| 53786 | 156 | |
| 157 | ||
| 51619 | 158 | /* icon */ | 
| 159 | ||
| 160 | def isabelle_icon(): ImageIcon = | |
| 54676 
6b2ca4850b71
uniform use of transparent icons, as for main "apps";
 wenzelm parents: 
54659diff
changeset | 161 |     new ImageIcon(getClass.getClassLoader.getResource("isabelle/isabelle_transparent-32.gif"))
 | 
| 51619 | 162 | |
| 54709 | 163 | def isabelle_icons(): List[ImageIcon] = | 
| 164 |     for (icon <- List("isabelle/isabelle_transparent-32.gif", "isabelle/isabelle_transparent.gif"))
 | |
| 165 | yield new ImageIcon(getClass.getClassLoader.getResource(icon)) | |
| 166 | ||
| 51619 | 167 | def isabelle_image(): Image = isabelle_icon().getImage | 
| 53712 | 168 | |
| 169 | ||
| 72974 | 170 | /* location within multi-screen environment */ | 
| 171 | ||
| 172 | final case class Screen_Location(point: Point, bounds: Rectangle) | |
| 173 |   {
 | |
| 174 | def relative(parent: Component, size: Dimension): Point = | |
| 175 |     {
 | |
| 176 | val w = size.width | |
| 177 | val h = size.height | |
| 178 | ||
| 179 | val x0 = parent.getLocationOnScreen.x | |
| 180 | val y0 = parent.getLocationOnScreen.y | |
| 181 | val x1 = x0 + parent.getWidth - w | |
| 182 | val y1 = y0 + parent.getHeight - h | |
| 183 | val x2 = point.x min (bounds.x + bounds.width - w) | |
| 184 | val y2 = point.y min (bounds.y + bounds.height - h) | |
| 185 | ||
| 186 | val location = new Point((x2 min x1) max x0, (y2 min y1) max y0) | |
| 187 | SwingUtilities.convertPointFromScreen(location, parent) | |
| 188 | location | |
| 189 | } | |
| 190 | } | |
| 191 | ||
| 192 | def screen_location(component: Component, point: Point): Screen_Location = | |
| 193 |   {
 | |
| 194 | val screen_point = new Point(point.x, point.y) | |
| 195 | if (component != null) SwingUtilities.convertPointToScreen(screen_point, component) | |
| 196 | ||
| 197 | val ge = GraphicsEnvironment.getLocalGraphicsEnvironment | |
| 198 | val screen_bounds = | |
| 199 |       (for {
 | |
| 200 | device <- ge.getScreenDevices.iterator | |
| 201 | config <- device.getConfigurations.iterator | |
| 202 | bounds = config.getBounds | |
| 203 | } yield bounds).find(_.contains(screen_point)) getOrElse ge.getMaximumWindowBounds | |
| 204 | ||
| 205 | Screen_Location(screen_point, screen_bounds) | |
| 206 | } | |
| 207 | ||
| 208 | def mouse_location(): Screen_Location = | |
| 209 | screen_location(null, MouseInfo.getPointerInfo.getLocation) | |
| 210 | ||
| 211 | ||
| 73037 | 212 | /* screen size */ | 
| 213 | ||
| 214 | sealed case class Screen_Size(bounds: Rectangle, insets: Insets) | |
| 215 |   {
 | |
| 73038 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 216 | def full_screen_bounds: Rectangle = | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 217 |       if (Platform.is_linux) {
 | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 218 | // avoid menu bar and docking areas | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 219 | new Rectangle( | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 220 | bounds.x + insets.left, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 221 | bounds.y + insets.top, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 222 | bounds.width - insets.left - insets.right, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 223 | bounds.height - insets.top - insets.bottom) | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 224 | } | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 225 |       else if (Platform.is_macos) {
 | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 226 | // avoid menu bar, but ignore docking areas | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 227 | new Rectangle( | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 228 | bounds.x, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 229 | bounds.y + insets.top, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 230 | bounds.width, | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 231 | bounds.height - insets.top) | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 232 | } | 
| 
3b14f7315dd2
some attempts at multi-platform full-screen mode;
 wenzelm parents: 
73037diff
changeset | 233 | else bounds | 
| 73037 | 234 | } | 
| 235 | ||
| 236 | def screen_size(component: Component): Screen_Size = | |
| 237 |   {
 | |
| 238 | val config = component.getGraphicsConfiguration | |
| 239 | val bounds = config.getBounds | |
| 240 | val insets = Toolkit.getDefaultToolkit.getScreenInsets(config) | |
| 241 | Screen_Size(bounds, insets) | |
| 242 | } | |
| 243 | ||
| 244 | ||
| 53712 | 245 | /* component hierachy */ | 
| 246 | ||
| 247 | def get_parent(component: Component): Option[Container] = | |
| 248 |     component.getParent match {
 | |
| 249 | case null => None | |
| 250 | case parent => Some(parent) | |
| 251 | } | |
| 252 | ||
| 253 |   def ancestors(component: Component): Iterator[Container] = new Iterator[Container] {
 | |
| 254 | private var next_elem = get_parent(component) | |
| 73337 | 255 | def hasNext: Boolean = next_elem.isDefined | 
| 53712 | 256 | def next(): Container = | 
| 257 |       next_elem match {
 | |
| 258 | case Some(parent) => | |
| 259 | next_elem = get_parent(parent) | |
| 260 | parent | |
| 261 | case None => Iterator.empty.next() | |
| 262 | } | |
| 263 | } | |
| 264 | ||
| 265 | def parent_window(component: Component): Option[Window] = | |
| 266 |     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 | 267 | |
| 
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 | 268 | 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 | 269 |     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 | 270 | 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 | 271 | 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 | 272 | 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 | 273 | 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 | 274 | } | 
| 53785 | 275 | |
| 73340 | 276 | def traverse_components(component: Component, apply: Component => Unit): Unit = | 
| 63874 | 277 |   {
 | 
| 73340 | 278 | def traverse(comp: Component): Unit = | 
| 63874 | 279 |     {
 | 
| 280 | apply(comp) | |
| 281 |       comp match {
 | |
| 282 | case cont: Container => | |
| 283 | for (i <- 0 until cont.getComponentCount) | |
| 284 | traverse(cont.getComponent(i)) | |
| 285 | case _ => | |
| 286 | } | |
| 287 | } | |
| 288 | traverse(component) | |
| 289 | } | |
| 290 | ||
| 53785 | 291 | |
| 292 | /* font operations */ | |
| 293 | ||
| 61742 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 294 | def copy_font(font: Font): Font = | 
| 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 295 | if (font == null) null | 
| 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 296 | else new Font(font.getFamily, font.getStyle, font.getSize) | 
| 
fd3b214b0979
clarified font: GUI defaults might change dynamically;
 wenzelm parents: 
61529diff
changeset | 297 | |
| 59230 | 298 | def line_metrics(font: Font): LineMetrics = | 
| 53785 | 299 |     font.getLineMetrics("", new FontRenderContext(null, false, false))
 | 
| 300 | ||
| 69376 | 301 | def transform_font(font: Font, transform: AffineTransform): Font = | 
| 71357 | 302 | font.deriveFont(java.util.Map.of(TextAttribute.TRANSFORM, new TransformAttribute(transform))) | 
| 69376 | 303 | |
| 304 | def font(family: String = Isabelle_Fonts.sans, size: Int = 1, bold: Boolean = false): Font = | |
| 305 | new Font(family, if (bold) Font.BOLD else Font.PLAIN, size) | |
| 306 | ||
| 69377 | 307 | def label_font(): Font = (new JLabel).getFont | 
| 308 | ||
| 69376 | 309 | |
| 310 | /* Isabelle fonts */ | |
| 311 | ||
| 69358 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 312 | def imitate_font(font: Font, | 
| 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 313 | family: String = Isabelle_Fonts.sans, | 
| 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 314 | scale: Double = 1.0): Font = | 
| 53785 | 315 |   {
 | 
| 316 | 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 | 317 | 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 | 318 | 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 | 319 | } | 
| 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 320 | |
| 69358 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 321 | def imitate_font_css(font: Font, | 
| 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 322 | family: String = Isabelle_Fonts.sans, | 
| 
71ef6e6da3dc
prefer Isabelle_Fonts.sans (not mono) as derived GUI font;
 wenzelm parents: 
69357diff
changeset | 323 | scale: Double = 1.0): String = | 
| 59183 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 324 |   {
 | 
| 
ec83638b6bfb
imitate font more carefully: err on smaller size;
 wenzelm parents: 
59080diff
changeset | 325 | val font1 = new Font(family, font.getStyle, font.getSize) | 
| 59230 | 326 | 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 | 327 | "font-family: " + family + "; font-size: " + (scale * rel_size * 100).toInt + "%;" | 
| 53785 | 328 | } | 
| 69377 | 329 | |
| 73340 | 330 | def use_isabelle_fonts(): Unit = | 
| 69377 | 331 |   {
 | 
| 332 | val default_font = label_font() | |
| 333 | val ui = UIManager.getDefaults | |
| 69384 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 334 | for (prop <- List( | 
| 72786 
21ff9c1a4644
more Isabelle fonts, notably for PanelWindowContainer buttons with Windows L&F;
 wenzelm parents: 
71361diff
changeset | 335 | "ToggleButton.font", | 
| 69384 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 336 | "CheckBoxMenuItem.font", | 
| 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 337 | "Label.font", | 
| 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 338 | "Menu.font", | 
| 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 339 | "MenuItem.font", | 
| 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 340 | "PopupMenu.font", | 
| 71360 
fcf5ee85743d
more Isabelle fonts, notably for File Browser title in GTK L&F;
 wenzelm parents: 
71357diff
changeset | 341 | "Table.font", | 
| 
fcf5ee85743d
more Isabelle fonts, notably for File Browser title in GTK L&F;
 wenzelm parents: 
71357diff
changeset | 342 | "TableHeader.font", | 
| 69384 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 343 | "TextArea.font", | 
| 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 344 | "TextField.font", | 
| 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 345 | "TextPane.font", | 
| 71361 | 346 | "ToolTip.font", | 
| 69384 
0c7d8b1b6594
more Isabelle fonts: CheckBoxMenuItem.font notably for Windows L&F;
 wenzelm parents: 
69378diff
changeset | 347 | "Tree.font")) | 
| 69377 | 348 |     {
 | 
| 349 |       val font = ui.get(prop) match { case font: Font => font case _ => default_font }
 | |
| 350 | ui.put(prop, GUI.imitate_font(font)) | |
| 351 | } | |
| 352 | } | |
| 51619 | 353 | } | 
| 73111 | 354 | |
| 355 | class FlatLightLaf extends GUI.Look_And_Feel(new com.formdev.flatlaf.FlatLightLaf) | |
| 356 | class FlatDarkLaf extends GUI.Look_And_Feel(new com.formdev.flatlaf.FlatDarkLaf) |