wenzelm@43381: /* Title: Tools/jEdit/src/text_area_painter.scala wenzelm@43369: Author: Makarius wenzelm@43369: wenzelm@43381: Painter setup for main jEdit text area. wenzelm@43369: */ wenzelm@43369: wenzelm@43369: package isabelle.jedit wenzelm@43369: wenzelm@43369: wenzelm@43369: import isabelle._ wenzelm@43369: wenzelm@43369: import java.awt.{Graphics, Graphics2D} wenzelm@43369: import java.util.ArrayList wenzelm@43369: wenzelm@43369: import org.gjt.sp.jedit.Debug wenzelm@43369: import org.gjt.sp.jedit.syntax.{DisplayTokenHandler, Chunk} wenzelm@43369: import org.gjt.sp.jedit.textarea.{JEditTextArea, TextAreaExtension, TextAreaPainter} wenzelm@43369: wenzelm@43369: wenzelm@43381: class Text_Area_Painter(doc_view: Document_View) wenzelm@43369: { wenzelm@43381: private val model = doc_view.model wenzelm@43376: private val text_area = doc_view.text_area wenzelm@43376: wenzelm@43369: private val orig_text_painter: TextAreaExtension = wenzelm@43369: { wenzelm@43369: val name = "org.gjt.sp.jedit.textarea.TextAreaPainter$PaintText" wenzelm@43369: text_area.getPainter.getExtensions.iterator.filter(x => x.getClass.getName == name).toList wenzelm@43369: match { wenzelm@43369: case List(x) => x wenzelm@43369: case _ => error("Expected exactly one " + name) wenzelm@43369: } wenzelm@43369: } wenzelm@43369: wenzelm@43381: wenzelm@43381: /* painter snapshot */ wenzelm@43381: wenzelm@43381: @volatile private var _painter_snapshot: Option[Document.Snapshot] = None wenzelm@43381: wenzelm@43381: private def painter_snapshot(): Document.Snapshot = wenzelm@43381: _painter_snapshot match { wenzelm@43381: case Some(snapshot) => snapshot wenzelm@43381: case None => error("Missing document snapshot for text area painter") wenzelm@43381: } wenzelm@43381: wenzelm@43381: private val set_snapshot = new TextAreaExtension wenzelm@43381: { wenzelm@43381: override def paintScreenLineRange(gfx: Graphics2D, wenzelm@43381: first_line: Int, last_line: Int, physical_lines: Array[Int], wenzelm@43381: start: Array[Int], end: Array[Int], y: Int, line_height: Int) wenzelm@43381: { wenzelm@43381: _painter_snapshot = Some(model.snapshot()) wenzelm@43381: } wenzelm@43381: } wenzelm@43381: wenzelm@43381: private val reset_snapshot = new TextAreaExtension wenzelm@43369: { wenzelm@43381: override def paintScreenLineRange(gfx: Graphics2D, wenzelm@43381: first_line: Int, last_line: Int, physical_lines: Array[Int], wenzelm@43381: start: Array[Int], end: Array[Int], y: Int, line_height: Int) wenzelm@43381: { wenzelm@43381: _painter_snapshot = None wenzelm@43381: } wenzelm@43381: } wenzelm@43381: wenzelm@43381: wenzelm@43381: /* text background */ wenzelm@43376: wenzelm@43381: private val background_painter = new TextAreaExtension wenzelm@43381: { wenzelm@43381: override def paintScreenLineRange(gfx: Graphics2D, wenzelm@43381: first_line: Int, last_line: Int, physical_lines: Array[Int], wenzelm@43381: start: Array[Int], end: Array[Int], y: Int, line_height: Int) wenzelm@43381: { wenzelm@43381: Isabelle.swing_buffer_lock(model.buffer) { wenzelm@43381: val snapshot = painter_snapshot() wenzelm@43381: val ascent = text_area.getPainter.getFontMetrics.getAscent wenzelm@43376: wenzelm@43381: for (i <- 0 until physical_lines.length) { wenzelm@43381: if (physical_lines(i) != -1) { wenzelm@43381: val line_range = doc_view.proper_line_range(start(i), end(i)) wenzelm@43376: wenzelm@43381: // background color: status wenzelm@43381: val cmds = snapshot.node.command_range(snapshot.revert(line_range)) wenzelm@43381: for { wenzelm@43381: (command, command_start) <- cmds if !command.is_ignored wenzelm@43381: val range = line_range.restrict(snapshot.convert(command.range + command_start)) wenzelm@43381: r <- Isabelle.gfx_range(text_area, range) wenzelm@43381: color <- Isabelle_Markup.status_color(snapshot, command) wenzelm@43381: } { wenzelm@43381: gfx.setColor(color) wenzelm@43381: gfx.fillRect(r.x, y + i * line_height, r.length, line_height) wenzelm@43381: } wenzelm@43376: wenzelm@43381: // background color (1): markup wenzelm@43381: for { wenzelm@43381: Text.Info(range, Some(color)) <- wenzelm@43381: snapshot.select_markup(line_range)(Isabelle_Markup.background1).iterator wenzelm@43381: r <- Isabelle.gfx_range(text_area, range) wenzelm@43381: } { wenzelm@43381: gfx.setColor(color) wenzelm@43381: gfx.fillRect(r.x, y + i * line_height, r.length, line_height) wenzelm@43381: } wenzelm@43376: wenzelm@43381: // background color (2): markup wenzelm@43381: for { wenzelm@43381: Text.Info(range, Some(color)) <- wenzelm@43381: snapshot.select_markup(line_range)(Isabelle_Markup.background2).iterator wenzelm@43381: r <- Isabelle.gfx_range(text_area, range) wenzelm@43381: } { wenzelm@43381: gfx.setColor(color) wenzelm@43381: gfx.fillRect(r.x + 2, y + i * line_height + 2, r.length - 4, line_height - 4) wenzelm@43376: } wenzelm@43381: wenzelm@43381: // sub-expression highlighting -- potentially from other snapshot wenzelm@43381: doc_view.highlight_range match { wenzelm@43381: case Some((range, color)) if line_range.overlaps(range) => wenzelm@43381: Isabelle.gfx_range(text_area, line_range.restrict(range)) match { wenzelm@43381: case None => wenzelm@43381: case Some(r) => wenzelm@43381: gfx.setColor(color) wenzelm@43381: gfx.drawRect(r.x, y + i * line_height, r.length - 1, line_height - 1) wenzelm@43381: } wenzelm@43381: case _ => wenzelm@43381: } wenzelm@43381: wenzelm@43381: // squiggly underline wenzelm@43381: for { wenzelm@43381: Text.Info(range, Some(color)) <- wenzelm@43381: snapshot.select_markup(line_range)(Isabelle_Markup.message).iterator wenzelm@43381: r <- Isabelle.gfx_range(text_area, range) wenzelm@43381: } { wenzelm@43381: gfx.setColor(color) wenzelm@43381: val x0 = (r.x / 2) * 2 wenzelm@43381: val y0 = r.y + ascent + 1 wenzelm@43381: for (x1 <- Range(x0, x0 + r.length, 2)) { wenzelm@43381: val y1 = if (x1 % 4 < 2) y0 else y0 + 1 wenzelm@43381: gfx.drawLine(x1, y1, x1 + 1, y1) wenzelm@43376: } wenzelm@43376: } wenzelm@43376: } wenzelm@43376: } wenzelm@43376: } wenzelm@43381: } wenzelm@43381: } wenzelm@43381: wenzelm@43381: wenzelm@43381: /* text */ wenzelm@43369: wenzelm@43381: private val text_painter = new TextAreaExtension wenzelm@43381: { wenzelm@43381: override def paintScreenLineRange(gfx: Graphics2D, wenzelm@43381: first_line: Int, last_line: Int, physical_lines: Array[Int], wenzelm@43381: start: Array[Int], end: Array[Int], y_start: Int, line_height: Int) wenzelm@43381: { wenzelm@43381: val buffer = text_area.getBuffer wenzelm@43381: Isabelle.swing_buffer_lock(buffer) { wenzelm@43381: val painter = text_area.getPainter wenzelm@43381: val font = painter.getFont wenzelm@43381: val font_context = painter.getFontRenderContext wenzelm@43381: val font_metrics = painter.getFontMetrics wenzelm@43371: wenzelm@43381: def paint_chunk_list(head_offset: Text.Offset, head: Chunk, x: Float, y: Float): Float = wenzelm@43381: { wenzelm@43381: val clip_rect = gfx.getClipBounds wenzelm@43381: wenzelm@43381: var w = 0.0f wenzelm@43381: var offset = head_offset wenzelm@43381: var chunk = head wenzelm@43381: while (chunk != null) { wenzelm@43381: val chunk_length = if (chunk.str == null) 0 else chunk.str.length wenzelm@43381: wenzelm@43381: if (x + w + chunk.width > clip_rect.x && wenzelm@43381: x + w < clip_rect.x + clip_rect.width && wenzelm@43381: chunk.accessable && chunk.visible) wenzelm@43381: { wenzelm@43381: val chunk_range = Text.Range(offset, offset + chunk_length) wenzelm@43381: val chunk_font = chunk.style.getFont wenzelm@43381: val chunk_color = chunk.style.getForegroundColor wenzelm@43381: wenzelm@43381: val markup = painter_snapshot.select_markup(chunk_range)(Isabelle_Markup.foreground) wenzelm@43371: wenzelm@43381: gfx.setFont(chunk_font) wenzelm@43381: if (!Debug.DISABLE_GLYPH_VECTOR && chunk.gv != null && wenzelm@43381: markup.forall(info => info.info.isEmpty)) { wenzelm@43381: gfx.setColor(chunk_color) wenzelm@43381: gfx.drawGlyphVector(chunk.gv, x + w, y) wenzelm@43381: } wenzelm@43381: else { wenzelm@43381: var xpos = x + w wenzelm@43381: for (Text.Info(range, info) <- markup) { wenzelm@43381: val str = chunk.str.substring(range.start - offset, range.stop - offset) wenzelm@43381: gfx.setColor(info.getOrElse(chunk_color)) wenzelm@43381: gfx.drawString(str, xpos.toInt, y.toInt) wenzelm@43381: xpos += chunk_font.getStringBounds(str, font_context).getWidth.toFloat wenzelm@43381: } wenzelm@43381: } wenzelm@43381: } wenzelm@43381: w += chunk.width wenzelm@43381: offset += chunk_length wenzelm@43381: chunk = chunk.next.asInstanceOf[Chunk] wenzelm@43381: } wenzelm@43381: w wenzelm@43381: } wenzelm@43371: wenzelm@43381: // see org.gjt.sp.jedit.textarea.TextArea.propertiesChanged wenzelm@43381: // see org.gjt.sp.jedit.textarea.TextArea.setMaxLineLength wenzelm@43381: val char_width = font.getStringBounds(" ", font_context).getWidth.round.toInt wenzelm@43381: val soft_wrap = buffer.getStringProperty("wrap") == "soft" wenzelm@43381: val wrap_margin = wenzelm@43381: { wenzelm@43381: val max = buffer.getIntegerProperty("maxLineLen", 0) wenzelm@43381: if (max > 0) font.getStringBounds(" " * max, font_context).getWidth.toInt wenzelm@43381: else if (soft_wrap) painter.getWidth - char_width * 3 wenzelm@43381: else 0 wenzelm@43381: }.toFloat wenzelm@43369: wenzelm@43381: val line_infos: Map[Text.Offset, Chunk] = wenzelm@43381: { wenzelm@43381: val out = new ArrayList[Chunk] wenzelm@43381: val handler = new DisplayTokenHandler wenzelm@43381: val margin = if (soft_wrap) wrap_margin else 0.0f wenzelm@43381: wenzelm@43381: var result = Map[Text.Offset, Chunk]() wenzelm@43381: for (line <- physical_lines.iterator.filter(i => i != -1)) { wenzelm@43381: out.clear wenzelm@43381: handler.init(painter.getStyles, font_context, painter, out, margin) wenzelm@43381: buffer.markTokens(line, handler) wenzelm@43381: wenzelm@43381: val line_start = buffer.getLineStartOffset(line) wenzelm@43381: for (i <- 0 until out.size) { wenzelm@43381: val chunk = out.get(i) wenzelm@43381: result += (line_start + chunk.offset -> chunk) wenzelm@43381: } wenzelm@43381: } wenzelm@43381: result wenzelm@43381: } wenzelm@43376: wenzelm@43381: val clip = gfx.getClip wenzelm@43381: val x0 = text_area.getHorizontalOffset wenzelm@43381: var y0 = wenzelm@43381: y_start + font_metrics.getHeight - (font_metrics.getLeading + 1) - font_metrics.getDescent wenzelm@43372: wenzelm@43381: for (i <- 0 until physical_lines.length) { wenzelm@43381: if (physical_lines(i) != -1) { wenzelm@43381: line_infos.get(start(i)) match { wenzelm@43381: case Some(chunk) => wenzelm@43381: val w = paint_chunk_list(start(i), chunk, x0, y0).toInt wenzelm@43381: gfx.clipRect(x0 + w, 0, Integer.MAX_VALUE, Integer.MAX_VALUE) wenzelm@43381: orig_text_painter.paintValidLine(gfx, wenzelm@43381: first_line + i, physical_lines(i), start(i), end(i), y_start + line_height * i) wenzelm@43381: gfx.setClip(clip) wenzelm@43381: wenzelm@43381: case None => wenzelm@43381: } wenzelm@43369: } wenzelm@43381: y0 += line_height wenzelm@43369: } wenzelm@43369: } wenzelm@43369: } wenzelm@43369: } wenzelm@43369: wenzelm@43369: wenzelm@43369: /* activation */ wenzelm@43369: wenzelm@43369: def activate() wenzelm@43369: { wenzelm@43369: val painter = text_area.getPainter wenzelm@43381: painter.addExtension(TextAreaPainter.LOWEST_LAYER, set_snapshot) wenzelm@43381: painter.addExtension(TextAreaPainter.LINE_BACKGROUND_LAYER + 1, background_painter) wenzelm@43381: painter.addExtension(TextAreaPainter.TEXT_LAYER, text_painter) wenzelm@43381: painter.addExtension(TextAreaPainter.HIGHEST_LAYER, reset_snapshot) wenzelm@43369: painter.removeExtension(orig_text_painter) wenzelm@43369: } wenzelm@43369: wenzelm@43369: def deactivate() wenzelm@43369: { wenzelm@43369: val painter = text_area.getPainter wenzelm@43369: painter.addExtension(TextAreaPainter.TEXT_LAYER, orig_text_painter) wenzelm@43381: painter.removeExtension(reset_snapshot) wenzelm@43381: painter.removeExtension(text_painter) wenzelm@43381: painter.removeExtension(background_painter) wenzelm@43381: painter.removeExtension(set_snapshot) wenzelm@43369: } wenzelm@43369: } wenzelm@43369: