wenzelm@34411
|
1 |
/*
|
wenzelm@34787
|
2 |
* Document view connected to jEdit text area
|
wenzelm@34411
|
3 |
*
|
wenzelm@34411
|
4 |
* @author Fabian Immler, TU Munich
|
wenzelm@34787
|
5 |
* @author Makarius
|
wenzelm@34411
|
6 |
*/
|
wenzelm@34411
|
7 |
|
immler@34406
|
8 |
package isabelle.jedit
|
immler@34406
|
9 |
|
wenzelm@34763
|
10 |
|
wenzelm@34826
|
11 |
import isabelle.proofdocument.{Command, Document, Session}
|
immler@34406
|
12 |
|
wenzelm@34787
|
13 |
import scala.actors.Actor._
|
wenzelm@34787
|
14 |
|
wenzelm@34787
|
15 |
import java.awt.event.{MouseAdapter, MouseEvent}
|
wenzelm@34787
|
16 |
import java.awt.{BorderLayout, Graphics, Dimension, Color, Graphics2D}
|
wenzelm@34737
|
17 |
import javax.swing.{JPanel, ToolTipManager}
|
wenzelm@34787
|
18 |
import javax.swing.event.{CaretListener, CaretEvent}
|
wenzelm@34737
|
19 |
|
wenzelm@34712
|
20 |
import org.gjt.sp.jedit.gui.RolloverButton
|
wenzelm@34787
|
21 |
import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea, TextAreaExtension, TextAreaPainter}
|
immler@34406
|
22 |
|
wenzelm@34736
|
23 |
|
wenzelm@34787
|
24 |
object Document_View
|
immler@34657
|
25 |
{
|
wenzelm@34826
|
26 |
def choose_color(command: Command, doc: Document): Color =
|
immler@34681
|
27 |
{
|
wenzelm@34870
|
28 |
doc.current_state(command).map(_.status) match {
|
wenzelm@34870
|
29 |
case Some(Command.Status.UNPROCESSED) => new Color(255, 228, 225)
|
wenzelm@34870
|
30 |
case Some(Command.Status.FINISHED) => new Color(234, 248, 255)
|
wenzelm@34870
|
31 |
case Some(Command.Status.FAILED) => new Color(255, 193, 193)
|
wenzelm@34787
|
32 |
case _ => Color.red
|
wenzelm@34737
|
33 |
}
|
immler@34681
|
34 |
}
|
immler@34516
|
35 |
|
immler@34406
|
36 |
|
wenzelm@34787
|
37 |
/* document view of text area */
|
immler@34406
|
38 |
|
wenzelm@34787
|
39 |
private val key = new Object
|
wenzelm@34787
|
40 |
|
wenzelm@34787
|
41 |
def init(model: Document_Model, text_area: TextArea): Document_View =
|
wenzelm@34787
|
42 |
{
|
wenzelm@34792
|
43 |
Swing_Thread.assert()
|
wenzelm@34787
|
44 |
val doc_view = new Document_View(model, text_area)
|
wenzelm@34787
|
45 |
text_area.putClientProperty(key, doc_view)
|
wenzelm@34787
|
46 |
doc_view.activate()
|
wenzelm@34787
|
47 |
doc_view
|
wenzelm@34787
|
48 |
}
|
wenzelm@34787
|
49 |
|
wenzelm@34791
|
50 |
def apply(text_area: TextArea): Option[Document_View] =
|
wenzelm@34787
|
51 |
{
|
wenzelm@34792
|
52 |
Swing_Thread.assert()
|
wenzelm@34787
|
53 |
text_area.getClientProperty(key) match {
|
wenzelm@34787
|
54 |
case doc_view: Document_View => Some(doc_view)
|
wenzelm@34787
|
55 |
case _ => None
|
wenzelm@34787
|
56 |
}
|
wenzelm@34787
|
57 |
}
|
wenzelm@34787
|
58 |
|
wenzelm@34787
|
59 |
def exit(text_area: TextArea)
|
wenzelm@34787
|
60 |
{
|
wenzelm@34792
|
61 |
Swing_Thread.assert()
|
wenzelm@34791
|
62 |
apply(text_area) match {
|
wenzelm@34787
|
63 |
case None => error("No document view for text area: " + text_area)
|
wenzelm@34787
|
64 |
case Some(doc_view) =>
|
wenzelm@34787
|
65 |
doc_view.deactivate()
|
wenzelm@34787
|
66 |
text_area.putClientProperty(key, null)
|
wenzelm@34787
|
67 |
}
|
wenzelm@34787
|
68 |
}
|
wenzelm@34787
|
69 |
}
|
wenzelm@34787
|
70 |
|
wenzelm@34787
|
71 |
|
wenzelm@34787
|
72 |
class Document_View(model: Document_Model, text_area: TextArea)
|
wenzelm@34787
|
73 |
{
|
wenzelm@34787
|
74 |
private val session = model.session
|
wenzelm@34787
|
75 |
|
wenzelm@34787
|
76 |
|
wenzelm@34837
|
77 |
/* visible document -- owned by Swing thread */
|
wenzelm@34837
|
78 |
|
wenzelm@34837
|
79 |
@volatile private var document = model.recent_document()
|
wenzelm@34837
|
80 |
|
wenzelm@34837
|
81 |
|
wenzelm@34837
|
82 |
/* buffer of changed commands -- owned by Swing thread */
|
wenzelm@34837
|
83 |
|
wenzelm@34837
|
84 |
@volatile private var changed_commands: Set[Command] = Set()
|
wenzelm@34837
|
85 |
|
wenzelm@34837
|
86 |
private val changed_delay = Swing_Thread.delay_first(100) {
|
wenzelm@34837
|
87 |
if (!changed_commands.isEmpty) {
|
wenzelm@34837
|
88 |
document = model.recent_document()
|
wenzelm@34837
|
89 |
for (cmd <- changed_commands if document.commands.contains(cmd)) { // FIXME cover doc states as well!!?
|
wenzelm@34837
|
90 |
update_syntax(cmd)
|
wenzelm@34837
|
91 |
invalidate_line(cmd)
|
wenzelm@34837
|
92 |
overview.repaint()
|
wenzelm@34837
|
93 |
}
|
wenzelm@34837
|
94 |
changed_commands = Set()
|
wenzelm@34837
|
95 |
}
|
wenzelm@34837
|
96 |
}
|
wenzelm@34837
|
97 |
|
wenzelm@34837
|
98 |
|
wenzelm@34787
|
99 |
/* command change actor */
|
wenzelm@34787
|
100 |
|
wenzelm@34787
|
101 |
private case object Exit
|
wenzelm@34787
|
102 |
|
wenzelm@34787
|
103 |
private val command_change_actor = actor {
|
wenzelm@34787
|
104 |
loop {
|
wenzelm@34787
|
105 |
react {
|
wenzelm@34837
|
106 |
case command: Command => // FIXME rough filtering according to document family!?
|
wenzelm@34837
|
107 |
Swing_Thread.now {
|
wenzelm@34837
|
108 |
changed_commands += command
|
wenzelm@34837
|
109 |
changed_delay()
|
wenzelm@34837
|
110 |
}
|
wenzelm@34787
|
111 |
|
wenzelm@34787
|
112 |
case Exit => reply(()); exit()
|
wenzelm@34787
|
113 |
|
wenzelm@34787
|
114 |
case bad => System.err.println("command_change_actor: ignoring bad message " + bad)
|
wenzelm@34787
|
115 |
}
|
wenzelm@34787
|
116 |
}
|
wenzelm@34787
|
117 |
}
|
wenzelm@34787
|
118 |
|
wenzelm@34787
|
119 |
|
wenzelm@34787
|
120 |
/* text_area_extension */
|
wenzelm@34787
|
121 |
|
wenzelm@34787
|
122 |
private val text_area_extension = new TextAreaExtension
|
wenzelm@34787
|
123 |
{
|
wenzelm@34787
|
124 |
override def paintValidLine(gfx: Graphics2D,
|
wenzelm@34787
|
125 |
screen_line: Int, physical_line: Int, start: Int, end: Int, y: Int)
|
wenzelm@34787
|
126 |
{
|
wenzelm@34787
|
127 |
def from_current(pos: Int) = model.from_current(document, pos)
|
wenzelm@34787
|
128 |
def to_current(pos: Int) = model.to_current(document, pos)
|
wenzelm@34858
|
129 |
val metrics = text_area.getPainter.getFontMetrics
|
wenzelm@34787
|
130 |
val saved_color = gfx.getColor
|
wenzelm@34858
|
131 |
try {
|
wenzelm@34858
|
132 |
for ((command, command_start) <-
|
wenzelm@34858
|
133 |
document.command_range(from_current(start), from_current(end)))
|
wenzelm@34858
|
134 |
{
|
wenzelm@34858
|
135 |
val begin = start max to_current(command_start)
|
wenzelm@34858
|
136 |
val finish = (end - 1) min to_current(command_start + command.length)
|
wenzelm@34858
|
137 |
encolor(gfx, y, metrics.getHeight, begin, finish,
|
wenzelm@34858
|
138 |
Document_View.choose_color(command, document), true)
|
wenzelm@34858
|
139 |
}
|
wenzelm@34787
|
140 |
}
|
wenzelm@34858
|
141 |
finally { gfx.setColor(saved_color) }
|
wenzelm@34787
|
142 |
}
|
wenzelm@34787
|
143 |
|
wenzelm@34787
|
144 |
override def getToolTipText(x: Int, y: Int): String =
|
wenzelm@34787
|
145 |
{
|
wenzelm@34787
|
146 |
val offset = model.from_current(document, text_area.xyToOffset(x, y))
|
wenzelm@34787
|
147 |
document.command_at(offset) match {
|
wenzelm@34858
|
148 |
case Some((command, command_start)) =>
|
wenzelm@34870
|
149 |
document.current_state(command).get.type_at(offset - command_start).getOrElse(null)
|
wenzelm@34787
|
150 |
case None => null
|
wenzelm@34787
|
151 |
}
|
wenzelm@34787
|
152 |
}
|
wenzelm@34787
|
153 |
}
|
wenzelm@34787
|
154 |
|
wenzelm@34787
|
155 |
|
wenzelm@34813
|
156 |
/* caret_listener */
|
wenzelm@34813
|
157 |
|
wenzelm@34813
|
158 |
private var _selected_command: Command = null
|
wenzelm@34814
|
159 |
private def selected_command = _selected_command
|
wenzelm@34814
|
160 |
private def selected_command_=(cmd: Command)
|
wenzelm@34813
|
161 |
{
|
wenzelm@34814
|
162 |
_selected_command = cmd
|
wenzelm@34814
|
163 |
session.results.event(cmd)
|
wenzelm@34813
|
164 |
}
|
wenzelm@34813
|
165 |
|
wenzelm@34813
|
166 |
private val caret_listener = new CaretListener
|
wenzelm@34813
|
167 |
{
|
wenzelm@34858
|
168 |
override def caretUpdate(e: CaretEvent)
|
wenzelm@34858
|
169 |
{
|
wenzelm@34837
|
170 |
document.command_at(e.getDot) match {
|
wenzelm@34858
|
171 |
case Some((command, command_start)) if (selected_command != command) =>
|
wenzelm@34858
|
172 |
selected_command = command
|
wenzelm@34813
|
173 |
case _ =>
|
wenzelm@34813
|
174 |
}
|
wenzelm@34813
|
175 |
}
|
wenzelm@34813
|
176 |
}
|
wenzelm@34813
|
177 |
|
wenzelm@34813
|
178 |
|
wenzelm@34787
|
179 |
/* (re)painting */
|
wenzelm@34787
|
180 |
|
wenzelm@34787
|
181 |
private val update_delay = Swing_Thread.delay_first(500) { model.buffer.propertiesChanged() }
|
wenzelm@34787
|
182 |
|
wenzelm@34787
|
183 |
private def update_syntax(cmd: Command)
|
wenzelm@34787
|
184 |
{
|
wenzelm@34837
|
185 |
val (line1, line2) = model.lines_of_command(document, cmd)
|
wenzelm@34787
|
186 |
if (line2 >= text_area.getFirstLine &&
|
wenzelm@34787
|
187 |
line1 <= text_area.getFirstLine + text_area.getVisibleLines)
|
wenzelm@34787
|
188 |
update_delay()
|
wenzelm@34787
|
189 |
}
|
wenzelm@34787
|
190 |
|
wenzelm@34787
|
191 |
private def invalidate_line(cmd: Command) =
|
wenzelm@34787
|
192 |
{
|
wenzelm@34837
|
193 |
val (start, stop) = model.lines_of_command(document, cmd)
|
wenzelm@34787
|
194 |
text_area.invalidateLineRange(start, stop)
|
wenzelm@34787
|
195 |
|
wenzelm@34813
|
196 |
if (selected_command == cmd)
|
wenzelm@34837
|
197 |
session.results.event(cmd)
|
wenzelm@34787
|
198 |
}
|
wenzelm@34787
|
199 |
|
wenzelm@34787
|
200 |
private def invalidate_all() =
|
wenzelm@34787
|
201 |
text_area.invalidateLineRange(text_area.getFirstPhysicalLine,
|
wenzelm@34787
|
202 |
text_area.getLastPhysicalLine)
|
wenzelm@34787
|
203 |
|
wenzelm@34787
|
204 |
private def encolor(gfx: Graphics2D,
|
wenzelm@34787
|
205 |
y: Int, height: Int, begin: Int, finish: Int, color: Color, fill: Boolean)
|
wenzelm@34787
|
206 |
{
|
wenzelm@34787
|
207 |
val start = text_area.offsetToXY(begin)
|
wenzelm@34787
|
208 |
val stop =
|
wenzelm@34787
|
209 |
if (finish < model.buffer.getLength) text_area.offsetToXY(finish)
|
wenzelm@34787
|
210 |
else {
|
wenzelm@34787
|
211 |
val p = text_area.offsetToXY(finish - 1)
|
wenzelm@34787
|
212 |
val metrics = text_area.getPainter.getFontMetrics
|
wenzelm@34787
|
213 |
p.x = p.x + (metrics.charWidth(' ') max metrics.getMaxAdvance)
|
wenzelm@34787
|
214 |
p
|
wenzelm@34787
|
215 |
}
|
wenzelm@34787
|
216 |
|
wenzelm@34787
|
217 |
if (start != null && stop != null) {
|
wenzelm@34787
|
218 |
gfx.setColor(color)
|
wenzelm@34787
|
219 |
if (fill) gfx.fillRect(start.x, y, stop.x - start.x, height)
|
wenzelm@34787
|
220 |
else gfx.drawRect(start.x, y, stop.x - start.x, height)
|
wenzelm@34787
|
221 |
}
|
wenzelm@34787
|
222 |
}
|
wenzelm@34787
|
223 |
|
wenzelm@34787
|
224 |
|
wenzelm@34787
|
225 |
/* overview of command status left of scrollbar */
|
wenzelm@34787
|
226 |
|
wenzelm@34837
|
227 |
private val overview = new JPanel(new BorderLayout)
|
wenzelm@34787
|
228 |
{
|
wenzelm@34787
|
229 |
private val WIDTH = 10
|
wenzelm@34787
|
230 |
private val HEIGHT = 2
|
wenzelm@34787
|
231 |
|
wenzelm@34809
|
232 |
setPreferredSize(new Dimension(WIDTH, 0))
|
wenzelm@34809
|
233 |
|
wenzelm@34787
|
234 |
setRequestFocusEnabled(false)
|
wenzelm@34787
|
235 |
|
wenzelm@34787
|
236 |
addMouseListener(new MouseAdapter {
|
wenzelm@34787
|
237 |
override def mousePressed(event: MouseEvent) {
|
wenzelm@34787
|
238 |
val line = y_to_line(event.getY)
|
wenzelm@34787
|
239 |
if (line >= 0 && line < text_area.getLineCount)
|
wenzelm@34787
|
240 |
text_area.setCaretPosition(text_area.getLineStartOffset(line))
|
wenzelm@34787
|
241 |
}
|
wenzelm@34787
|
242 |
})
|
wenzelm@34787
|
243 |
|
wenzelm@34787
|
244 |
override def addNotify() {
|
wenzelm@34787
|
245 |
super.addNotify()
|
wenzelm@34787
|
246 |
ToolTipManager.sharedInstance.registerComponent(this)
|
wenzelm@34787
|
247 |
}
|
wenzelm@34787
|
248 |
|
wenzelm@34787
|
249 |
override def removeNotify() {
|
wenzelm@34787
|
250 |
ToolTipManager.sharedInstance.unregisterComponent(this)
|
wenzelm@34787
|
251 |
super.removeNotify
|
wenzelm@34787
|
252 |
}
|
wenzelm@34787
|
253 |
|
wenzelm@34787
|
254 |
override def getToolTipText(event: MouseEvent): String =
|
wenzelm@34787
|
255 |
{
|
wenzelm@34787
|
256 |
val line = y_to_line(event.getY())
|
wenzelm@34787
|
257 |
if (line >= 0 && line < text_area.getLineCount) "<html><b>TODO:</b><br>Tooltip</html>"
|
wenzelm@34787
|
258 |
else ""
|
wenzelm@34787
|
259 |
}
|
wenzelm@34787
|
260 |
|
wenzelm@34787
|
261 |
override def paintComponent(gfx: Graphics)
|
wenzelm@34787
|
262 |
{
|
wenzelm@34787
|
263 |
super.paintComponent(gfx)
|
wenzelm@34787
|
264 |
val buffer = model.buffer
|
wenzelm@34787
|
265 |
|
wenzelm@34859
|
266 |
for ((command, start) <- document.command_range(0)) {
|
wenzelm@34859
|
267 |
val line1 = buffer.getLineOfOffset(model.to_current(document, start))
|
wenzelm@34859
|
268 |
val line2 = buffer.getLineOfOffset(model.to_current(document, start + command.length)) + 1
|
wenzelm@34787
|
269 |
val y = line_to_y(line1)
|
wenzelm@34787
|
270 |
val height = HEIGHT * (line2 - line1)
|
wenzelm@34837
|
271 |
gfx.setColor(Document_View.choose_color(command, document))
|
wenzelm@34787
|
272 |
gfx.fillRect(0, y, getWidth - 1, height)
|
wenzelm@34787
|
273 |
}
|
wenzelm@34787
|
274 |
}
|
wenzelm@34787
|
275 |
|
wenzelm@34787
|
276 |
private def line_to_y(line: Int): Int =
|
wenzelm@34787
|
277 |
(line * getHeight) / (text_area.getBuffer.getLineCount max text_area.getVisibleLines)
|
wenzelm@34787
|
278 |
|
wenzelm@34787
|
279 |
private def y_to_line(y: Int): Int =
|
wenzelm@34787
|
280 |
(y * (text_area.getBuffer.getLineCount max text_area.getVisibleLines)) / getHeight
|
wenzelm@34787
|
281 |
}
|
wenzelm@34787
|
282 |
|
wenzelm@34787
|
283 |
|
wenzelm@34787
|
284 |
/* activation */
|
wenzelm@34787
|
285 |
|
wenzelm@34811
|
286 |
private def activate()
|
wenzelm@34787
|
287 |
{
|
wenzelm@34787
|
288 |
text_area.getPainter.
|
wenzelm@34787
|
289 |
addExtension(TextAreaPainter.LINE_BACKGROUND_LAYER + 1, text_area_extension)
|
wenzelm@34813
|
290 |
text_area.addCaretListener(caret_listener)
|
wenzelm@34813
|
291 |
text_area.addLeftOfScrollBar(overview)
|
wenzelm@34787
|
292 |
session.command_change += command_change_actor
|
wenzelm@34787
|
293 |
}
|
wenzelm@34787
|
294 |
|
wenzelm@34811
|
295 |
private def deactivate()
|
wenzelm@34787
|
296 |
{
|
wenzelm@34787
|
297 |
session.command_change -= command_change_actor
|
wenzelm@34787
|
298 |
command_change_actor !? Exit
|
wenzelm@34813
|
299 |
text_area.removeLeftOfScrollBar(overview)
|
wenzelm@34813
|
300 |
text_area.removeCaretListener(caret_listener)
|
wenzelm@34787
|
301 |
text_area.getPainter.removeExtension(text_area_extension)
|
wenzelm@34787
|
302 |
}
|
immler@34406
|
303 |
} |