2 * Document view connected to jEdit text area
4 * @author Fabian Immler, TU Munich
11 import isabelle.proofdocument.{Command, Document, Session}
13 import scala.actors.Actor._
15 import java.awt.event.{MouseAdapter, MouseEvent}
16 import java.awt.{BorderLayout, Graphics, Dimension, Color, Graphics2D}
17 import javax.swing.{JPanel, ToolTipManager}
18 import javax.swing.event.{CaretListener, CaretEvent}
20 import org.gjt.sp.jedit.gui.RolloverButton
21 import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea, TextAreaExtension, TextAreaPainter}
26 def choose_color(command: Command, doc: Document): Color =
28 doc.current_state(command).status match {
29 case Command.Status.UNPROCESSED => new Color(255, 228, 225)
30 case Command.Status.FINISHED => new Color(234, 248, 255)
31 case Command.Status.FAILED => new Color(255, 193, 193)
37 /* document view of text area */
39 private val key = new Object
41 def init(model: Document_Model, text_area: TextArea): Document_View =
44 val doc_view = new Document_View(model, text_area)
45 text_area.putClientProperty(key, doc_view)
50 def apply(text_area: TextArea): Option[Document_View] =
53 text_area.getClientProperty(key) match {
54 case doc_view: Document_View => Some(doc_view)
59 def exit(text_area: TextArea)
62 apply(text_area) match {
63 case None => error("No document view for text area: " + text_area)
64 case Some(doc_view) =>
66 text_area.putClientProperty(key, null)
72 class Document_View(model: Document_Model, text_area: TextArea)
74 private val session = model.session
77 /* visible document -- owned by Swing thread */
79 @volatile private var document = model.recent_document()
82 /* buffer of changed commands -- owned by Swing thread */
84 @volatile private var changed_commands: Set[Command] = Set()
86 private val changed_delay = Swing_Thread.delay_first(100) {
87 if (!changed_commands.isEmpty) {
88 document = model.recent_document()
89 for (cmd <- changed_commands if document.commands.contains(cmd)) { // FIXME cover doc states as well!!?
94 changed_commands = Set()
99 /* command change actor */
101 private case object Exit
103 private val command_change_actor = actor {
106 case command: Command => // FIXME rough filtering according to document family!?
108 changed_commands += command
112 case Exit => reply(()); exit()
114 case bad => System.err.println("command_change_actor: ignoring bad message " + bad)
120 /* text_area_extension */
122 private val text_area_extension = new TextAreaExtension
124 override def paintValidLine(gfx: Graphics2D,
125 screen_line: Int, physical_line: Int, start: Int, end: Int, y: Int)
127 def from_current(pos: Int) = model.from_current(document, pos)
128 def to_current(pos: Int) = model.to_current(document, pos)
129 val metrics = text_area.getPainter.getFontMetrics
130 val saved_color = gfx.getColor
132 for ((command, command_start) <-
133 document.command_range(from_current(start), from_current(end)))
135 val begin = start max to_current(command_start)
136 val finish = (end - 1) min to_current(command_start + command.length)
137 encolor(gfx, y, metrics.getHeight, begin, finish,
138 Document_View.choose_color(command, document), true)
141 finally { gfx.setColor(saved_color) }
144 override def getToolTipText(x: Int, y: Int): String =
146 val offset = model.from_current(document, text_area.xyToOffset(x, y))
147 document.command_at(offset) match {
148 case Some((command, command_start)) =>
149 document.current_state(command).type_at(offset - command_start).getOrElse(null)
158 private var _selected_command: Command = null
159 private def selected_command = _selected_command
160 private def selected_command_=(cmd: Command)
162 _selected_command = cmd
163 session.results.event(cmd)
166 private val caret_listener = new CaretListener
168 override def caretUpdate(e: CaretEvent)
170 document.command_at(e.getDot) match {
171 case Some((command, command_start)) if (selected_command != command) =>
172 selected_command = command
181 private val update_delay = Swing_Thread.delay_first(500) { model.buffer.propertiesChanged() }
183 private def update_syntax(cmd: Command)
185 val (line1, line2) = model.lines_of_command(document, cmd)
186 if (line2 >= text_area.getFirstLine &&
187 line1 <= text_area.getFirstLine + text_area.getVisibleLines)
191 private def invalidate_line(cmd: Command) =
193 val (start, stop) = model.lines_of_command(document, cmd)
194 text_area.invalidateLineRange(start, stop)
196 if (selected_command == cmd)
197 session.results.event(cmd)
200 private def invalidate_all() =
201 text_area.invalidateLineRange(text_area.getFirstPhysicalLine,
202 text_area.getLastPhysicalLine)
204 private def encolor(gfx: Graphics2D,
205 y: Int, height: Int, begin: Int, finish: Int, color: Color, fill: Boolean)
207 val start = text_area.offsetToXY(begin)
209 if (finish < model.buffer.getLength) text_area.offsetToXY(finish)
211 val p = text_area.offsetToXY(finish - 1)
212 val metrics = text_area.getPainter.getFontMetrics
213 p.x = p.x + (metrics.charWidth(' ') max metrics.getMaxAdvance)
217 if (start != null && stop != null) {
219 if (fill) gfx.fillRect(start.x, y, stop.x - start.x, height)
220 else gfx.drawRect(start.x, y, stop.x - start.x, height)
225 /* overview of command status left of scrollbar */
227 private val overview = new JPanel(new BorderLayout)
229 private val WIDTH = 10
230 private val HEIGHT = 2
232 setPreferredSize(new Dimension(WIDTH, 0))
234 setRequestFocusEnabled(false)
236 addMouseListener(new MouseAdapter {
237 override def mousePressed(event: MouseEvent) {
238 val line = y_to_line(event.getY)
239 if (line >= 0 && line < text_area.getLineCount)
240 text_area.setCaretPosition(text_area.getLineStartOffset(line))
244 override def addNotify() {
246 ToolTipManager.sharedInstance.registerComponent(this)
249 override def removeNotify() {
250 ToolTipManager.sharedInstance.unregisterComponent(this)
254 override def getToolTipText(event: MouseEvent): String =
256 val line = y_to_line(event.getY())
257 if (line >= 0 && line < text_area.getLineCount) "<html><b>TODO:</b><br>Tooltip</html>"
261 override def paintComponent(gfx: Graphics)
263 super.paintComponent(gfx)
264 val buffer = model.buffer
266 for (command <- document.commands) {
267 val line1 = buffer.getLineOfOffset(model.to_current(document, command.start(document)))
268 val line2 = buffer.getLineOfOffset(model.to_current(document, command.stop(document))) + 1
269 val y = line_to_y(line1)
270 val height = HEIGHT * (line2 - line1)
271 gfx.setColor(Document_View.choose_color(command, document))
272 gfx.fillRect(0, y, getWidth - 1, height)
276 private def line_to_y(line: Int): Int =
277 (line * getHeight) / (text_area.getBuffer.getLineCount max text_area.getVisibleLines)
279 private def y_to_line(y: Int): Int =
280 (y * (text_area.getBuffer.getLineCount max text_area.getVisibleLines)) / getHeight
286 private def activate()
288 text_area.getPainter.
289 addExtension(TextAreaPainter.LINE_BACKGROUND_LAYER + 1, text_area_extension)
290 text_area.addCaretListener(caret_listener)
291 text_area.addLeftOfScrollBar(overview)
292 session.command_change += command_change_actor
295 private def deactivate()
297 session.command_change -= command_change_actor
298 command_change_actor !? Exit
299 text_area.removeLeftOfScrollBar(overview)
300 text_area.removeCaretListener(caret_listener)
301 text_area.getPainter.removeExtension(text_area_extension)