| author | nipkow | 
| Mon, 13 Jan 2014 07:33:51 +0100 | |
| changeset 55003 | c65fd9218ea1 | 
| parent 54965 | a8af7a9c38d1 | 
| child 56622 | 891d1b8b64fb | 
| 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 | 
| 53853 
e8430d668f44
more quasi-generic PIDE modules (NB: Swing/JFX needs to be kept separate from non-GUI material);
 wenzelm parents: 
53850diff
changeset | 2 | Module: PIDE-GUI | 
| 51619 | 3 | Author: Makarius | 
| 4 | ||
| 53712 | 5 | Basic GUI tools (for AWT/Swing). | 
| 51619 | 6 | */ | 
| 7 | ||
| 8 | package isabelle | |
| 9 | ||
| 10 | ||
| 54962 | 11 | import java.lang.{ClassLoader, ClassNotFoundException, NoSuchMethodException}
 | 
| 53785 | 12 | import java.awt.{Image, Component, Container, Toolkit, Window, Font}
 | 
| 13 | import java.awt.font.{TextAttribute, TransformAttribute, FontRenderContext, LineMetrics}
 | |
| 14 | import java.awt.geom.AffineTransform | |
| 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 | 15 | import javax.swing.{ImageIcon, JOptionPane, UIManager, JLayeredPane, JFrame, JWindow}
 | 
| 51619 | 16 | |
| 54709 | 17 | import scala.collection.convert.WrapAsJava | 
| 51619 | 18 | import scala.swing.{ComboBox, TextArea, ScrollPane}
 | 
| 19 | import scala.swing.event.SelectionChanged | |
| 20 | ||
| 21 | ||
| 22 | object GUI | |
| 23 | {
 | |
| 24 | /* Swing look-and-feel */ | |
| 25 | ||
| 26 | def get_laf(): String = | |
| 27 |   {
 | |
| 28 | if (Platform.is_windows || Platform.is_macos) | |
| 29 | UIManager.getSystemLookAndFeelClassName() | |
| 30 | else | |
| 53850 
b1bc857f2422
simplified default L&F -- Nimbus should be always available and GTK+ is not fully working yet;
 wenzelm parents: 
53849diff
changeset | 31 | UIManager.getInstalledLookAndFeels().find(_.getName == "Nimbus").map(_.getClassName) | 
| 
b1bc857f2422
simplified default L&F -- Nimbus should be always available and GTK+ is not fully working yet;
 wenzelm parents: 
53849diff
changeset | 32 | .getOrElse(UIManager.getCrossPlatformLookAndFeelClassName()) | 
| 51619 | 33 | } | 
| 34 | ||
| 35 | def init_laf(): Unit = UIManager.setLookAndFeel(get_laf()) | |
| 36 | ||
| 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 | 37 | def is_macos_laf(): Boolean = | 
| 53849 | 38 | Platform.is_macos && | 
| 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 | 39 | UIManager.getSystemLookAndFeelClassName() == UIManager.getLookAndFeel.getClass.getName | 
| 
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 | 40 | |
| 51619 | 41 | |
| 54962 | 42 | /* X11 window manager */ | 
| 43 | ||
| 44 | def window_manager(): Option[String] = | |
| 45 |   {
 | |
| 54965 | 46 | if (Platform.is_windows || Platform.is_macos) None | 
| 47 | else | |
| 48 |       try {
 | |
| 49 |         val XWM = Class.forName("sun.awt.X11.XWM", true, ClassLoader.getSystemClassLoader)
 | |
| 50 |         val getWM = XWM.getDeclaredMethod("getWM")
 | |
| 51 | getWM.setAccessible(true) | |
| 52 |         getWM.invoke(null) match {
 | |
| 53 | case null => None | |
| 54 | case wm => Some(wm.toString) | |
| 55 | } | |
| 54962 | 56 | } | 
| 54965 | 57 |       catch {
 | 
| 58 | case _: ClassNotFoundException => None | |
| 59 | case _: NoSuchMethodException => None | |
| 60 | } | |
| 54962 | 61 | } | 
| 62 | ||
| 63 | ||
| 51619 | 64 | /* simple dialogs */ | 
| 65 | ||
| 53714 | 66 | def scrollable_text(txt: String, width: Int = 60, height: Int = 20, editable: Boolean = false) | 
| 67 | : ScrollPane = | |
| 51619 | 68 |   {
 | 
| 69 | val text = new TextArea(txt) | |
| 70 | if (width > 0) text.columns = width | |
| 53714 | 71 | if (height > 0 && split_lines(txt).length > height) text.rows = height | 
| 51619 | 72 | text.editable = editable | 
| 73 | new ScrollPane(text) | |
| 74 | } | |
| 75 | ||
| 76 | private def simple_dialog(kind: Int, default_title: String, | |
| 77 | parent: Component, title: String, message: Seq[Any]) | |
| 78 |   {
 | |
| 79 |     Swing_Thread.now {
 | |
| 80 |       val java_message = message map { case x: scala.swing.Component => x.peer case x => x }
 | |
| 81 | JOptionPane.showMessageDialog(parent, | |
| 82 | java_message.toArray.asInstanceOf[Array[AnyRef]], | |
| 83 | if (title == null) default_title else title, kind) | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | def dialog(parent: Component, title: String, message: Any*) = | |
| 88 | simple_dialog(JOptionPane.PLAIN_MESSAGE, null, parent, title, message) | |
| 89 | ||
| 90 | def warning_dialog(parent: Component, title: String, message: Any*) = | |
| 91 | simple_dialog(JOptionPane.WARNING_MESSAGE, "Warning", parent, title, message) | |
| 92 | ||
| 93 | def error_dialog(parent: Component, title: String, message: Any*) = | |
| 94 | simple_dialog(JOptionPane.ERROR_MESSAGE, "Error", parent, title, message) | |
| 95 | ||
| 96 | def confirm_dialog(parent: Component, title: String, option_type: Int, message: Any*): Int = | |
| 97 |     Swing_Thread.now {
 | |
| 98 |       val java_message = message map { case x: scala.swing.Component => x.peer case x => x }
 | |
| 99 | JOptionPane.showConfirmDialog(parent, | |
| 100 | java_message.toArray.asInstanceOf[Array[AnyRef]], title, | |
| 101 | option_type, JOptionPane.QUESTION_MESSAGE) | |
| 102 | } | |
| 103 | ||
| 104 | ||
| 105 | /* zoom box */ | |
| 106 | ||
| 107 | class Zoom_Box(apply_factor: Int => Unit) extends ComboBox[String]( | |
| 108 |     List("50%", "70%", "85%", "100%", "125%", "150%", "175%", "200%", "300%", "400%"))
 | |
| 109 |   {
 | |
| 110 | val Factor = "([0-9]+)%?".r | |
| 111 | def parse(text: String): Int = | |
| 112 |       text match {
 | |
| 113 | case Factor(s) => | |
| 114 | val i = Integer.parseInt(s) | |
| 115 | if (10 <= i && i <= 1000) i else 100 | |
| 116 | case _ => 100 | |
| 117 | } | |
| 118 | ||
| 119 | def print(i: Int): String = i.toString + "%" | |
| 120 | ||
| 121 |     def set_item(i: Int) {
 | |
| 122 |       peer.getEditor match {
 | |
| 123 | case null => | |
| 124 | case editor => editor.setItem(print(i)) | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | makeEditable()(c => new ComboBox.BuiltInEditor(c)(text => print(parse(text)), x => x)) | |
| 129 |     reactions += {
 | |
| 130 | case SelectionChanged(_) => apply_factor(parse(selection.item)) | |
| 131 | } | |
| 132 | listenTo(selection) | |
| 133 | selection.index = 3 | |
| 134 |     prototypeDisplayValue = Some("00000%")
 | |
| 135 | } | |
| 136 | ||
| 137 | ||
| 53786 | 138 | /* tooltip with multi-line support */ | 
| 139 | ||
| 140 | def tooltip_lines(lines: List[String]): String = | |
| 141 | if (lines.isEmpty) null | |
| 142 | else "<html><pre>" + HTML.encode(cat_lines(lines)) + "</pre></html>" | |
| 143 | ||
| 144 | ||
| 51619 | 145 | /* screen resolution */ | 
| 146 | ||
| 147 | def resolution_scale(): Double = Toolkit.getDefaultToolkit.getScreenResolution.toDouble / 72 | |
| 148 | def resolution_scale(i: Int): Int = (i.toDouble * resolution_scale()).round.toInt | |
| 149 | ||
| 150 | ||
| 151 | /* icon */ | |
| 152 | ||
| 153 | def isabelle_icon(): ImageIcon = | |
| 54676 
6b2ca4850b71
uniform use of transparent icons, as for main "apps";
 wenzelm parents: 
54659diff
changeset | 154 |     new ImageIcon(getClass.getClassLoader.getResource("isabelle/isabelle_transparent-32.gif"))
 | 
| 51619 | 155 | |
| 54709 | 156 | def isabelle_icons(): List[ImageIcon] = | 
| 157 |     for (icon <- List("isabelle/isabelle_transparent-32.gif", "isabelle/isabelle_transparent.gif"))
 | |
| 158 | yield new ImageIcon(getClass.getClassLoader.getResource(icon)) | |
| 159 | ||
| 51619 | 160 | def isabelle_image(): Image = isabelle_icon().getImage | 
| 53712 | 161 | |
| 54709 | 162 | def isabelle_images(): java.util.List[Image] = | 
| 163 | WrapAsJava.seqAsJavaList(isabelle_icons.map(_.getImage)) | |
| 164 | ||
| 53712 | 165 | |
| 166 | /* component hierachy */ | |
| 167 | ||
| 168 | def get_parent(component: Component): Option[Container] = | |
| 169 |     component.getParent match {
 | |
| 170 | case null => None | |
| 171 | case parent => Some(parent) | |
| 172 | } | |
| 173 | ||
| 174 |   def ancestors(component: Component): Iterator[Container] = new Iterator[Container] {
 | |
| 175 | private var next_elem = get_parent(component) | |
| 176 | def hasNext(): Boolean = next_elem.isDefined | |
| 177 | def next(): Container = | |
| 178 |       next_elem match {
 | |
| 179 | case Some(parent) => | |
| 180 | next_elem = get_parent(parent) | |
| 181 | parent | |
| 182 | case None => Iterator.empty.next() | |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | def parent_window(component: Component): Option[Window] = | |
| 187 |     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 | 188 | |
| 
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 | 189 | 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 | 190 |     parent_window(component) match {
 | 
| 
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 | 191 | case Some(window: JWindow) => Some(window.getLayeredPane) | 
| 
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 | 192 | case Some(frame: JFrame) => Some(frame.getLayeredPane) | 
| 
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 | 193 | 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 | 194 | } | 
| 53785 | 195 | |
| 196 | ||
| 197 | /* font operations */ | |
| 198 | ||
| 199 | def font_metrics(font: Font): LineMetrics = | |
| 200 |     font.getLineMetrics("", new FontRenderContext(null, false, false))
 | |
| 201 | ||
| 54368 | 202 | def imitate_font(family: String, font: Font, scale: Double = 1.0): Font = | 
| 53785 | 203 |   {
 | 
| 204 | val font1 = new Font(family, font.getStyle, font.getSize) | |
| 54368 | 205 | val size = scale * (font_metrics(font).getAscent / font_metrics(font1).getAscent * font.getSize) | 
| 206 | font1.deriveFont(size.round.toInt) | |
| 53785 | 207 | } | 
| 208 | ||
| 209 | def transform_font(font: Font, transform: AffineTransform): Font = | |
| 210 |   {
 | |
| 211 | import scala.collection.JavaConversions._ | |
| 212 | font.deriveFont(Map(TextAttribute.TRANSFORM -> new TransformAttribute(transform))) | |
| 213 | } | |
| 51619 | 214 | } | 
| 215 |