| author | wenzelm | 
| Sun, 26 Feb 2023 21:17:10 +0100 | |
| changeset 77386 | cae3d891adff | 
| parent 76782 | a62a609b5db2 | 
| child 80551 | 1638e980f737 | 
| permissions | -rw-r--r-- | 
| 49406 | 1 | /* Title: Tools/jEdit/src/jedit_lib.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 4 | Misc library functions for jEdit. | |
| 5 | */ | |
| 6 | ||
| 7 | package isabelle.jedit | |
| 8 | ||
| 9 | ||
| 10 | import isabelle._ | |
| 11 | ||
| 65469 | 12 | import java.io.{File => JFile}
 | 
| 72899 | 13 | import java.awt.{Component, Container, GraphicsEnvironment, Point, Rectangle, Dimension, Toolkit}
 | 
| 53784 | 14 | import java.awt.event.{InputEvent, KeyEvent, KeyListener}
 | 
| 53247 
bd595338ca18
uniform use of isabelle.jEdit.Popup, based on generic screen location operations;
 wenzelm parents: 
53237diff
changeset | 15 | import javax.swing.{Icon, ImageIcon, JWindow, SwingUtilities}
 | 
| 49710 
21d88a631fcc
refer to parent frame -- relevant for floating dockables in particular;
 wenzelm parents: 
49409diff
changeset | 16 | |
| 56823 
37be55461dbe
more frugal access to theory text via Reader, reduced costs for I/O text decoding;
 wenzelm parents: 
56774diff
changeset | 17 | import scala.util.parsing.input.CharSequenceReader | 
| 73354 | 18 | import scala.jdk.CollectionConverters._ | 
| 49406 | 19 | |
| 62264 
340f98836fd9
re-init document views for the sake of Text_Overview size;
 wenzelm parents: 
61865diff
changeset | 20 | import org.gjt.sp.jedit.{jEdit, Buffer, View, GUIUtilities, Debug, EditPane}
 | 
| 72927 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 21 | import org.gjt.sp.jedit.io.{FileVFS, VFSManager}
 | 
| 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 22 | import org.gjt.sp.jedit.gui.{KeyEventWorkaround, KeyEventTranslator}
 | 
| 61192 | 23 | import org.gjt.sp.jedit.buffer.{JEditBuffer, LineManager}
 | 
| 51492 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 24 | import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea, TextAreaPainter}
 | 
| 49406 | 25 | |
| 26 | ||
| 75393 | 27 | object JEdit_Lib {
 | 
| 71861 | 28 | /* jEdit directories */ | 
| 29 | ||
| 30 | def directories: List[JFile] = | |
| 31 | (Option(jEdit.getSettingsDirectory).toList ::: List(jEdit.getJEditHome)).map(new JFile(_)) | |
| 32 | ||
| 33 | ||
| 53019 | 34 | /* window geometry measurement */ | 
| 35 | ||
| 36 | private lazy val dummy_window = new JWindow | |
| 37 | ||
| 75393 | 38 |   final case class Window_Geometry(width: Int, height: Int, inner_width: Int, inner_height: Int) {
 | 
| 53019 | 39 | def deco_width: Int = width - inner_width | 
| 40 | def deco_height: Int = height - inner_height | |
| 41 | } | |
| 42 | ||
| 75393 | 43 |   def window_geometry(outer: Container, inner: Component): Window_Geometry = {
 | 
| 57612 
990ffb84489b
clarified module name: facilitate alternative GUI frameworks;
 wenzelm parents: 
56930diff
changeset | 44 |     GUI_Thread.require {}
 | 
| 53019 | 45 | |
| 46 | val old_content = dummy_window.getContentPane | |
| 47 | ||
| 48 | dummy_window.setContentPane(outer) | |
| 49 | dummy_window.pack | |
| 73367 | 50 | dummy_window.revalidate() | 
| 53019 | 51 | |
| 52 | val geometry = | |
| 53 | Window_Geometry( | |
| 54 | dummy_window.getWidth, dummy_window.getHeight, inner.getWidth, inner.getHeight) | |
| 55 | ||
| 56 | dummy_window.setContentPane(old_content) | |
| 57 | ||
| 58 | geometry | |
| 59 | } | |
| 60 | ||
| 61 | ||
| 76782 | 62 | /* plain files */ | 
| 65469 | 63 | |
| 64 | def is_file(name: String): Boolean = | |
| 76782 | 65 | name != null && name.nonEmpty && VFSManager.getVFSForPath(name).isInstanceOf[FileVFS] | 
| 65469 | 66 | |
| 67 | def check_file(name: String): Option[JFile] = | |
| 68 | if (is_file(name)) Some(new JFile(name)) else None | |
| 69 | ||
| 70 | ||
| 49406 | 71 | /* buffers */ | 
| 72 | ||
| 73 | def buffer_text(buffer: JEditBuffer): String = | |
| 74 |     buffer_lock(buffer) { buffer.getText(0, buffer.getLength) }
 | |
| 75 | ||
| 56823 
37be55461dbe
more frugal access to theory text via Reader, reduced costs for I/O text decoding;
 wenzelm parents: 
56774diff
changeset | 76 | def buffer_reader(buffer: JEditBuffer): CharSequenceReader = | 
| 64824 | 77 | Scan.char_reader(buffer.getSegment(0, buffer.getLength)) | 
| 56823 
37be55461dbe
more frugal access to theory text via Reader, reduced costs for I/O text decoding;
 wenzelm parents: 
56774diff
changeset | 78 | |
| 75393 | 79 |   def buffer_mode(buffer: JEditBuffer): String = {
 | 
| 53274 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 80 | val mode = buffer.getMode | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 81 | if (mode == null) "" | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 82 |     else {
 | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 83 | val name = mode.getName | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 84 | if (name == null) "" else name | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 85 | } | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 86 | } | 
| 
1760c01f1c78
maintain Completion_Popup.Text_Area as client property like Document_View;
 wenzelm parents: 
53247diff
changeset | 87 | |
| 65469 | 88 | def buffer_line_manager(buffer: JEditBuffer): LineManager = | 
| 89 | Untyped.get[LineManager](buffer, "lineMgr") | |
| 90 | ||
| 49406 | 91 | def buffer_name(buffer: Buffer): String = buffer.getSymlinkPath | 
| 92 | ||
| 65469 | 93 | def buffer_file(buffer: Buffer): Option[JFile] = check_file(buffer_name(buffer)) | 
| 61192 | 94 | |
| 75393 | 95 |   def buffer_undo_in_progress[A](buffer: JEditBuffer, body: => A): A = {
 | 
| 68060 
3931ed905e93
avoid output showing up in kill ring (via TextArea.setText, JEditBuffer.remove, UndoManager.contentRemoved), e.g. relevant for action "paste-deleted";
 wenzelm parents: 
67014diff
changeset | 96 | val undo_in_progress = buffer.isUndoInProgress | 
| 73340 | 97 | def set(b: Boolean): Unit = Untyped.set[Boolean](buffer, "undoInProgress", b) | 
| 68060 
3931ed905e93
avoid output showing up in kill ring (via TextArea.setText, JEditBuffer.remove, UndoManager.contentRemoved), e.g. relevant for action "paste-deleted";
 wenzelm parents: 
67014diff
changeset | 98 |     try { set(true); body } finally { set(undo_in_progress) }
 | 
| 
3931ed905e93
avoid output showing up in kill ring (via TextArea.setText, JEditBuffer.remove, UndoManager.contentRemoved), e.g. relevant for action "paste-deleted";
 wenzelm parents: 
67014diff
changeset | 99 | } | 
| 
3931ed905e93
avoid output showing up in kill ring (via TextArea.setText, JEditBuffer.remove, UndoManager.contentRemoved), e.g. relevant for action "paste-deleted";
 wenzelm parents: 
67014diff
changeset | 100 | |
| 49406 | 101 | |
| 102 | /* main jEdit components */ | |
| 103 | ||
| 73354 | 104 | def jedit_buffers(): Iterator[Buffer] = | 
| 105 | jEdit.getBufferManager().getBuffers().asScala.iterator | |
| 49406 | 106 | |
| 107 | def jedit_buffer(name: String): Option[Buffer] = | |
| 108 | jedit_buffers().find(buffer => buffer_name(buffer) == name) | |
| 109 | ||
| 56457 
eea4bbe15745
tuned signature -- prefer static type Document.Node.Name;
 wenzelm parents: 
55812diff
changeset | 110 | def jedit_buffer(name: Document.Node.Name): Option[Buffer] = | 
| 
eea4bbe15745
tuned signature -- prefer static type Document.Node.Name;
 wenzelm parents: 
55812diff
changeset | 111 | jedit_buffer(name.node) | 
| 
eea4bbe15745
tuned signature -- prefer static type Document.Node.Name;
 wenzelm parents: 
55812diff
changeset | 112 | |
| 73354 | 113 | def jedit_views(): Iterator[View] = | 
| 114 | jEdit.getViewManager().getViews().asScala.iterator | |
| 49406 | 115 | |
| 69636 | 116 | def jedit_view(view: View = null): View = | 
| 117 | if (view == null) jEdit.getActiveView() else view | |
| 118 | ||
| 62264 
340f98836fd9
re-init document views for the sake of Text_Overview size;
 wenzelm parents: 
61865diff
changeset | 119 | def jedit_edit_panes(view: View): Iterator[EditPane] = | 
| 
340f98836fd9
re-init document views for the sake of Text_Overview size;
 wenzelm parents: 
61865diff
changeset | 120 | if (view == null) Iterator.empty | 
| 
340f98836fd9
re-init document views for the sake of Text_Overview size;
 wenzelm parents: 
61865diff
changeset | 121 | else view.getEditPanes().iterator.filter(_ != null) | 
| 
340f98836fd9
re-init document views for the sake of Text_Overview size;
 wenzelm parents: 
61865diff
changeset | 122 | |
| 49406 | 123 | def jedit_text_areas(view: View): Iterator[JEditTextArea] = | 
| 56587 
83777a91f5de
clarified before_caret_range: prevent continuation on next line;
 wenzelm parents: 
56574diff
changeset | 124 | if (view == null) Iterator.empty | 
| 
83777a91f5de
clarified before_caret_range: prevent continuation on next line;
 wenzelm parents: 
56574diff
changeset | 125 | else view.getEditPanes().iterator.filter(_ != null).map(_.getTextArea).filter(_ != null) | 
| 49406 | 126 | |
| 127 | def jedit_text_areas(): Iterator[JEditTextArea] = | |
| 71601 | 128 | jedit_views().flatMap(jedit_text_areas) | 
| 49406 | 129 | |
| 130 | def jedit_text_areas(buffer: JEditBuffer): Iterator[JEditTextArea] = | |
| 131 | jedit_text_areas().filter(_.getBuffer == buffer) | |
| 132 | ||
| 75393 | 133 |   def buffer_lock[A](buffer: JEditBuffer)(body: => A): A = {
 | 
| 49406 | 134 |     try { buffer.readLock(); body }
 | 
| 135 |     finally { buffer.readUnlock() }
 | |
| 136 | } | |
| 49407 | 137 | |
| 75393 | 138 |   def buffer_edit[A](buffer: JEditBuffer)(body: => A): A = {
 | 
| 50195 
863b1dfc396c
prefer buffer_edit combinator over Java-style boilerplate;
 wenzelm parents: 
50186diff
changeset | 139 |     try { buffer.beginCompoundEdit(); body }
 | 
| 
863b1dfc396c
prefer buffer_edit combinator over Java-style boilerplate;
 wenzelm parents: 
50186diff
changeset | 140 |     finally { buffer.endCompoundEdit() }
 | 
| 
863b1dfc396c
prefer buffer_edit combinator over Java-style boilerplate;
 wenzelm parents: 
50186diff
changeset | 141 | } | 
| 
863b1dfc396c
prefer buffer_edit combinator over Java-style boilerplate;
 wenzelm parents: 
50186diff
changeset | 142 | |
| 49407 | 143 | |
| 50215 | 144 | /* get text */ | 
| 145 | ||
| 67014 | 146 | def get_text(buffer: JEditBuffer, range: Text.Range): Option[String] = | 
| 50215 | 147 |     try { Some(buffer.getText(range.start, range.length)) }
 | 
| 148 |     catch { case _: ArrayIndexOutOfBoundsException => None }
 | |
| 149 | ||
| 150 | ||
| 49407 | 151 | /* point range */ | 
| 152 | ||
| 153 | def point_range(buffer: JEditBuffer, offset: Text.Offset): Text.Range = | |
| 56592 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 154 | if (offset < 0) Text.Range.offside | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 155 | else | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 156 |       buffer_lock(buffer) {
 | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 157 | def text(i: Text.Offset): Char = buffer.getText(i, 1).charAt(0) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 158 |         try {
 | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 159 | val c = text(offset) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 160 | if (Character.isHighSurrogate(c) && Character.isLowSurrogate(text(offset + 1))) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 161 | Text.Range(offset, offset + 2) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 162 | else if (Character.isLowSurrogate(c) && Character.isHighSurrogate(text(offset - 1))) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 163 | Text.Range(offset - 1, offset + 1) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 164 | else | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 165 | Text.Range(offset, offset + 1) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 166 | } | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 167 |         catch { case _: ArrayIndexOutOfBoundsException => Text.Range(offset, offset + 1) }
 | 
| 49407 | 168 | } | 
| 49408 | 169 | |
| 170 | ||
| 55812 | 171 | /* text ranges */ | 
| 172 | ||
| 173 | def buffer_range(buffer: JEditBuffer): Text.Range = | |
| 174 | Text.Range(0, buffer.getLength) | |
| 49408 | 175 | |
| 56587 
83777a91f5de
clarified before_caret_range: prevent continuation on next line;
 wenzelm parents: 
56574diff
changeset | 176 | def line_range(buffer: JEditBuffer, line: Int): Text.Range = | 
| 56589 
71c5d1f516c0
more robust JEdit_Lib.line_range, according to usual jEdit confusion at end of last line;
 wenzelm parents: 
56587diff
changeset | 177 | Text.Range(buffer.getLineStartOffset(line), buffer.getLineEndOffset(line) min buffer.getLength) | 
| 56587 
83777a91f5de
clarified before_caret_range: prevent continuation on next line;
 wenzelm parents: 
56574diff
changeset | 178 | |
| 56592 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 179 | def caret_range(text_area: TextArea): Text.Range = | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 180 | point_range(text_area.getBuffer, text_area.getCaretPosition) | 
| 
5157f7615e99
prefer direct caret_range for update_dictionary actions, which usually happen outside the flow of editing;
 wenzelm parents: 
56589diff
changeset | 181 | |
| 75393 | 182 |   def visible_range(text_area: TextArea): Option[Text.Range] = {
 | 
| 49408 | 183 | val buffer = text_area.getBuffer | 
| 184 | val n = text_area.getVisibleLines | |
| 185 |     if (n > 0) {
 | |
| 186 | val start = text_area.getScreenLineStartOffset(0) | |
| 187 | val raw_end = text_area.getScreenLineEndOffset(n - 1) | |
| 49843 
afddf4e26fac
further refinement of jEdit line range, avoiding lack of final \n;
 wenzelm parents: 
49712diff
changeset | 188 | val end = if (raw_end >= 0) raw_end min buffer.getLength else buffer.getLength | 
| 
afddf4e26fac
further refinement of jEdit line range, avoiding lack of final \n;
 wenzelm parents: 
49712diff
changeset | 189 | Some(Text.Range(start, end)) | 
| 49408 | 190 | } | 
| 191 | else None | |
| 192 | } | |
| 193 | ||
| 75393 | 194 |   def invalidate_range(text_area: TextArea, range: Text.Range): Unit = {
 | 
| 49408 | 195 | val buffer = text_area.getBuffer | 
| 55812 | 196 |     buffer_range(buffer).try_restrict(range) match {
 | 
| 197 | case Some(range1) if !range1.is_singularity => | |
| 56699 | 198 |         try {
 | 
| 199 | text_area.invalidateLineRange( | |
| 200 | buffer.getLineOfOffset(range1.start), | |
| 201 | buffer.getLineOfOffset(range1.stop)) | |
| 202 | } | |
| 203 |         catch { case _: ArrayIndexOutOfBoundsException => }
 | |
| 55812 | 204 | case _ => | 
| 205 | } | |
| 49408 | 206 | } | 
| 49409 | 207 | |
| 75393 | 208 |   def invalidate(text_area: TextArea): Unit = {
 | 
| 62986 | 209 | val visible_lines = text_area.getVisibleLines | 
| 210 | if (visible_lines > 0) text_area.invalidateScreenLineRange(0, visible_lines) | |
| 211 | } | |
| 212 | ||
| 49409 | 213 | |
| 214 | /* graphics range */ | |
| 215 | ||
| 60215 | 216 | case class Gfx_Range(x: Int, y: Int, length: Int) | 
| 49409 | 217 | |
| 49843 
afddf4e26fac
further refinement of jEdit line range, avoiding lack of final \n;
 wenzelm parents: 
49712diff
changeset | 218 | // NB: jEdit always normalizes \r\n and \r to \n | 
| 49409 | 219 | // NB: last line lacks \n | 
| 75393 | 220 |   def gfx_range(text_area: TextArea, range: Text.Range): Option[Gfx_Range] = {
 | 
| 51507 | 221 | val metric = pretty_metric(text_area.getPainter) | 
| 51492 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 222 | val char_width = (metric.unit * metric.average).round.toInt | 
| 50849 | 223 | |
| 49409 | 224 | val buffer = text_area.getBuffer | 
| 225 | ||
| 226 | val end = buffer.getLength | |
| 227 | val stop = range.stop | |
| 51078 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 228 | |
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 229 | val (p, q, r) = | 
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 230 |       try {
 | 
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 231 | val p = text_area.offsetToXY(range.start) | 
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 232 | val (q, r) = | 
| 67014 | 233 |           if (get_text(buffer, Text.Range(stop - 1, stop)) == Some("\n"))
 | 
| 51078 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 234 | (text_area.offsetToXY(stop - 1), char_width) | 
| 55549 
5c40782f68b3
clarified special eol treatment (amending 3d55ef732cd7): allow last line to be empty, which means stop == end for second-last line;
 wenzelm parents: 
53786diff
changeset | 235 | else if (stop >= end) | 
| 
5c40782f68b3
clarified special eol treatment (amending 3d55ef732cd7): allow last line to be empty, which means stop == end for second-last line;
 wenzelm parents: 
53786diff
changeset | 236 | (text_area.offsetToXY(end), char_width * (stop - end)) | 
| 51078 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 237 | else (text_area.offsetToXY(stop), 0) | 
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 238 | (p, q, r) | 
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 239 | } | 
| 
4e1c940b1fb2
more robust JEdit_Lib.pixel_range, which could crash via Rich_Text_Area.tooltip_painter with bad mouse coordinates;
 wenzelm parents: 
50849diff
changeset | 240 |       catch { case _: ArrayIndexOutOfBoundsException => (null, null, 0) }
 | 
| 49409 | 241 | |
| 242 | if (p != null && q != null && p.x < q.x + r && p.y == q.y) | |
| 50115 | 243 | Some(Gfx_Range(p.x, p.y, q.x + r - p.x)) | 
| 49409 | 244 | else None | 
| 245 | } | |
| 49941 
f2db0596bd61
more precise pixel_range: avoid popup when pointing into empty space after actual end-of-line;
 wenzelm parents: 
49843diff
changeset | 246 | |
| 
f2db0596bd61
more precise pixel_range: avoid popup when pointing into empty space after actual end-of-line;
 wenzelm parents: 
49843diff
changeset | 247 | |
| 
f2db0596bd61
more precise pixel_range: avoid popup when pointing into empty space after actual end-of-line;
 wenzelm parents: 
49843diff
changeset | 248 | /* pixel range */ | 
| 
f2db0596bd61
more precise pixel_range: avoid popup when pointing into empty space after actual end-of-line;
 wenzelm parents: 
49843diff
changeset | 249 | |
| 75393 | 250 |   def pixel_range(text_area: TextArea, x: Int, y: Int): Option[Text.Range] = {
 | 
| 53183 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 251 | // coordinates wrt. inner painter component | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 252 | val painter = text_area.getPainter | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 253 |     if (0 <= x && x < painter.getWidth && 0 <= y && y < painter.getHeight) {
 | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 254 | val offset = text_area.xyToOffset(x, y, false) | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 255 |       if (offset >= 0) {
 | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 256 | val range = point_range(text_area.getBuffer, offset) | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 257 |         gfx_range(text_area, range) match {
 | 
| 60215 | 258 | case Some(g) if g.x <= x && x < g.x + g.length => Some(range) | 
| 53183 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 259 | case _ => None | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 260 | } | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 261 | } | 
| 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 262 | else None | 
| 49941 
f2db0596bd61
more precise pixel_range: avoid popup when pointing into empty space after actual end-of-line;
 wenzelm parents: 
49843diff
changeset | 263 | } | 
| 53183 
018d71bee930
strict checking of coordinates wrt. inner painter component;
 wenzelm parents: 
53019diff
changeset | 264 | else None | 
| 49941 
f2db0596bd61
more precise pixel_range: avoid popup when pointing into empty space after actual end-of-line;
 wenzelm parents: 
49843diff
changeset | 265 | } | 
| 51492 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 266 | |
| 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 267 | |
| 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 268 | /* pretty text metric */ | 
| 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 269 | |
| 75393 | 270 |   abstract class Pretty_Metric extends Pretty.Metric {
 | 
| 51507 | 271 | def average: Double | 
| 272 | } | |
| 51492 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 273 | |
| 51507 | 274 | def pretty_metric(painter: TextAreaPainter): Pretty_Metric = | 
| 275 |     new Pretty_Metric {
 | |
| 276 | def string_width(s: String): Double = | |
| 277 | painter.getFont.getStringBounds(s, painter.getFontRenderContext).getWidth | |
| 278 | ||
| 71601 | 279 | val unit: Double = string_width(Symbol.space) max 1.0 | 
| 280 |       val average: Double = string_width("mix") / (3 * unit)
 | |
| 51507 | 281 | def apply(s: String): Double = if (s == "\n") 1.0 else string_width(s) / unit | 
| 51492 
eaa1c4cc1106
more explicit Pretty.Metric, with clear distinction of unit (space width) vs. average char width (for visual adjustments) -- NB: Pretty formatting works via full space characters (despite a981a5c8a505 and 70f7483df9cb);
 wenzelm parents: 
51469diff
changeset | 282 | } | 
| 52873 | 283 | |
| 284 | ||
| 285 | /* icons */ | |
| 286 | ||
| 75393 | 287 |   def load_icon(name: String): Icon = {
 | 
| 52873 | 288 | val name1 = | 
| 289 |       if (name.startsWith("idea-icons/")) {
 | |
| 75701 | 290 |         val file = File.uri(Path.explode("$ISABELLE_IDEA_ICONS")).toASCIIString
 | 
| 52873 | 291 | "jar:" + file + "!/" + name | 
| 292 | } | |
| 293 | else name | |
| 294 | val icon = GUIUtilities.loadIcon(name1) | |
| 295 |     if (icon.getIconWidth < 0 || icon.getIconHeight < 0) error("Bad icon: " + name)
 | |
| 296 | else icon | |
| 297 | } | |
| 52874 | 298 | |
| 299 | def load_image_icon(name: String): ImageIcon = | |
| 300 |     load_icon(name) match {
 | |
| 301 | case icon: ImageIcon => icon | |
| 302 |       case _ => error("Bad image icon: " + name)
 | |
| 303 | } | |
| 53226 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 304 | |
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 305 | |
| 53231 
423e29f1f304
avoid complication and event duplication due to KeyEventInterceptor -- NB: popup has focus within root window, it is closed on loss of focus;
 wenzelm parents: 
53226diff
changeset | 306 | /* key event handling */ | 
| 
423e29f1f304
avoid complication and event duplication due to KeyEventInterceptor -- NB: popup has focus within root window, it is closed on loss of focus;
 wenzelm parents: 
53226diff
changeset | 307 | |
| 75393 | 308 |   def request_focus_view(alt_view: View = null): Unit = {
 | 
| 73987 
fc363a3b690a
build.props for isabelle.jar, including isabelle.jedit;
 wenzelm parents: 
73652diff
changeset | 309 | val view = if (alt_view != null) alt_view else jEdit.getActiveView() | 
| 
fc363a3b690a
build.props for isabelle.jar, including isabelle.jedit;
 wenzelm parents: 
73652diff
changeset | 310 |     if (view != null) {
 | 
| 
fc363a3b690a
build.props for isabelle.jar, including isabelle.jedit;
 wenzelm parents: 
73652diff
changeset | 311 | val text_area = view.getTextArea | 
| 
fc363a3b690a
build.props for isabelle.jar, including isabelle.jedit;
 wenzelm parents: 
73652diff
changeset | 312 | if (text_area != null) text_area.requestFocus() | 
| 
fc363a3b690a
build.props for isabelle.jar, including isabelle.jedit;
 wenzelm parents: 
73652diff
changeset | 313 | } | 
| 
fc363a3b690a
build.props for isabelle.jar, including isabelle.jedit;
 wenzelm parents: 
73652diff
changeset | 314 | } | 
| 66592 | 315 | |
| 75393 | 316 |   def propagate_key(view: View, evt: KeyEvent): Unit = {
 | 
| 53231 
423e29f1f304
avoid complication and event duplication due to KeyEventInterceptor -- NB: popup has focus within root window, it is closed on loss of focus;
 wenzelm parents: 
53226diff
changeset | 317 | if (view != null && !evt.isConsumed) | 
| 
423e29f1f304
avoid complication and event duplication due to KeyEventInterceptor -- NB: popup has focus within root window, it is closed on loss of focus;
 wenzelm parents: 
53226diff
changeset | 318 | view.getInputHandler().processKeyEvent(evt, View.ACTION_BAR, false) | 
| 
423e29f1f304
avoid complication and event duplication due to KeyEventInterceptor -- NB: popup has focus within root window, it is closed on loss of focus;
 wenzelm parents: 
53226diff
changeset | 319 | } | 
| 53226 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 320 | |
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 321 | def key_listener( | 
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 322 | key_typed: KeyEvent => Unit = _ => (), | 
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 323 | key_pressed: KeyEvent => Unit = _ => (), | 
| 75393 | 324 | key_released: KeyEvent => Unit = _ => () | 
| 325 |   ): KeyListener = {
 | |
| 326 |     def process_key_event(evt0: KeyEvent, handle: KeyEvent => Unit): Unit = {
 | |
| 53237 | 327 | val evt = KeyEventWorkaround.processKeyEvent(evt0) | 
| 53226 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 328 | if (evt != null) handle(evt) | 
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 329 | } | 
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 330 | |
| 75393 | 331 |     new KeyListener {
 | 
| 73340 | 332 | def keyTyped(evt: KeyEvent): Unit = process_key_event(evt, key_typed) | 
| 333 | def keyPressed(evt: KeyEvent): Unit = process_key_event(evt, key_pressed) | |
| 334 | def keyReleased(evt: KeyEvent): Unit = process_key_event(evt, key_released) | |
| 53226 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 335 | } | 
| 
9cf8e2263ca7
more systematic JEdit_Lib.key_listener with optional KeyEventWorkaround;
 wenzelm parents: 
53183diff
changeset | 336 | } | 
| 53784 | 337 | |
| 75393 | 338 |   def special_key(evt: KeyEvent): Boolean = {
 | 
| 59571 | 339 | // cf. 5.2.0/jEdit/org/gjt/sp/jedit/gui/KeyEventWorkaround.java | 
| 71500 | 340 | val mod = evt.getModifiersEx | 
| 341 | (mod & InputEvent.CTRL_DOWN_MASK) != 0 && (mod & InputEvent.ALT_DOWN_MASK) == 0 || | |
| 342 | (mod & InputEvent.CTRL_DOWN_MASK) == 0 && (mod & InputEvent.ALT_DOWN_MASK) != 0 && | |
| 53784 | 343 | !Debug.ALT_KEY_PRESSED_DISABLED || | 
| 71500 | 344 | (mod & InputEvent.META_DOWN_MASK) != 0 | 
| 53784 | 345 | } | 
| 72899 | 346 | |
| 347 | def command_modifier(evt: InputEvent): Boolean = | |
| 348 | (evt.getModifiersEx & Toolkit.getDefaultToolkit.getMenuShortcutKeyMaskEx) != 0 | |
| 349 | ||
| 350 | def shift_modifier(evt: InputEvent): Boolean = | |
| 351 | (evt.getModifiersEx & InputEvent.SHIFT_DOWN_MASK) != 0 | |
| 72927 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 352 | |
| 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 353 | def modifier_string(evt: InputEvent): String = | 
| 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 354 |     KeyEventTranslator.getModifierString(evt) match {
 | 
| 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 355 | case null => "" | 
| 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 356 | case s => s | 
| 
69f768aff611
clarified caret focus modifier, depending on option "jedit_focus_modifier";
 wenzelm parents: 
72899diff
changeset | 357 | } | 
| 49406 | 358 | } |