/* * 03/07/2012 * FoldingAwareIconRowHeader - Icon row header that paints itself correctly * even when code folding is enabled. * This library is distributed under a modified BSD license. See the included * RSyntaxTextArea.License.txt file for details. */ package org.fife.ui.rsyntaxtextarea; import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import javax.swing.Icon; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import org.fife.ui.rsyntaxtextarea.folding.FoldManager; import org.fife.ui.rtextarea.GutterIconInfo; import org.fife.ui.rtextarea.IconRowHeader; /** * A row header component that takes code folding into account when painting * itself. * * @author Robert Futrell * @version 1.0 */ public class FoldingAwareIconRowHeader extends IconRowHeader { /** * Constructor. * * @param textArea The parent text area. */ public FoldingAwareIconRowHeader(RSyntaxTextArea textArea) { super(textArea); } /** * {@inheritDoc} */ @Override protected void paintComponent(Graphics g) { // When line wrap is not enabled, take the faster code path. if (textArea == null) { return; } RSyntaxTextArea rsta = (RSyntaxTextArea) textArea; FoldManager fm = rsta.getFoldManager(); if (!fm.isCodeFoldingSupportedAndEnabled()) { super.paintComponent(g); return; } visibleRect = g.getClipBounds(visibleRect); if (visibleRect == null) { // ??? visibleRect = getVisibleRect(); } // System.out.println("IconRowHeader repainting: " + visibleRect); if (visibleRect == null) { return; } paintBackgroundImpl(g, visibleRect); if (textArea.getLineWrap()) { paintComponentWrapped(g); return; } Document doc = textArea.getDocument(); Element root = doc.getDefaultRootElement(); textAreaInsets = textArea.getInsets(textAreaInsets); if (visibleRect.y < textAreaInsets.top) { visibleRect.height -= (textAreaInsets.top - visibleRect.y); visibleRect.y = textAreaInsets.top; } // Get the first line to paint. int cellHeight = textArea.getLineHeight(); int topLine = (visibleRect.y - textAreaInsets.top) / cellHeight; // Get where to start painting (top of the row). // We need to be "scrolled up" up just enough for the missing part of // the first line. int y = topLine * cellHeight + textAreaInsets.top; // AFTER calculating visual offset to paint at, account for folding. topLine += fm.getHiddenLineCountAbove(topLine, true); // Paint the active line range. if (activeLineRangeStart > -1 && activeLineRangeEnd > -1) { Color activeLineRangeColor = getActiveLineRangeColor(); g.setColor(activeLineRangeColor); try { int realY1 = rsta.yForLine(activeLineRangeStart); if (realY1 > -1) { // Not in a collapsed fold... int y1 = realY1;// Math.max(y, realY1); int y2 = rsta.yForLine(activeLineRangeEnd); if (y2 == -1) { // In a collapsed fold y2 = y1; } y2 += cellHeight - 1; if (y2 < visibleRect.y || y1 > visibleRect.y + visibleRect.height) { // System.out.println("... nothing to paint, // bailing..."); return; } y1 = Math.max(y, realY1); y2 = Math.min(y2, visibleRect.y + visibleRect.height); // System.out.println(y1 + "... " + y2 + "; " + realY1 + ", // " + visibleRect); int j = y1; while (j <= y2) { int yEnd = Math.min(y2, j + getWidth()); int xEnd = yEnd - j; g.drawLine(0, j, xEnd, yEnd); j += 2; } int i = 2; while (i < getWidth()) { int yEnd = y1 + getWidth() - i; g.drawLine(i, y1, getWidth(), yEnd); i += 2; } if (realY1 >= y && realY1 < visibleRect.y + visibleRect.height) { g.drawLine(0, realY1, getWidth(), realY1); } if (y2 >= y && y2 < visibleRect.y + visibleRect.height) { g.drawLine(0, y2, getWidth(), y2); } } } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } } // Paint icons if (trackingIcons != null) { int lastLine = textArea.getLineCount() - 1; for (int i = trackingIcons.size() - 1; i >= 0; i--) { // Last to // first GutterIconInfo ti = getTrackingIcon(i); int offs = ti.getMarkedOffset(); if (offs >= 0 && offs <= doc.getLength()) { int line = root.getElementIndex(offs); if (line <= lastLine && line >= topLine) { try { Icon icon = ti.getIcon(); if (icon != null) { int lineY = rsta.yForLine(line); if (lineY >= y && lineY <= visibleRect.y + visibleRect.height) { int y2 = lineY + (cellHeight - icon.getIconHeight()) / 2; icon.paintIcon(this, g, 0, y2); lastLine = line - 1; // Paint only 1 icon // per line } } } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } } else if (line < topLine) { break; // All other lines are above us, so quit now } } } } } /** * Paints icons when line wrapping is enabled. Note that this does not override * the parent class's implementation to avoid this version being called when * line wrapping is disabled. */ private void paintComponentWrapped(Graphics g) { // The variables we use are as follows: // - visibleRect is the "visible" area of the text area; e.g. // [0,100, 300,100+(lineCount*cellHeight)-1]. // actualTop.y is the topmost-pixel in the first logical line we // paint. Note that we may well not paint this part of the logical // line, as it may be broken into many physical lines, with the first // few physical lines scrolled past. Note also that this is NOT the // visible rect of this line number list; this line number list has // visible rect == [0,0, insets.left-1,visibleRect.height-1]. // We avoid using modelToView/viewToModel where possible, as these // methods trigger a parsing of the line into syntax tokens, which is // costly. It's cheaper to just grab the child views' bounds. RSyntaxTextArea rsta = (RSyntaxTextArea) textArea; // boolean currentLineHighlighted = textArea.getHighlightCurrentLine(); Document doc = textArea.getDocument(); Element root = doc.getDefaultRootElement(); int topPosition = textArea.viewToModel(new Point(visibleRect.x, visibleRect.y)); int topLine = root.getElementIndex(topPosition); int topY = visibleRect.y; int bottomY = visibleRect.y + visibleRect.height; int cellHeight = textArea.getLineHeight(); // Paint icons if (trackingIcons != null) { int lastLine = textArea.getLineCount() - 1; for (int i = trackingIcons.size() - 1; i >= 0; i--) { // Last to // first GutterIconInfo ti = getTrackingIcon(i); Icon icon = ti.getIcon(); if (icon != null) { int iconH = icon.getIconHeight(); int offs = ti.getMarkedOffset(); if (offs >= 0 && offs <= doc.getLength()) { int line = root.getElementIndex(offs); if (line <= lastLine && line >= topLine) { try { int lineY = rsta.yForLine(line); if (lineY <= bottomY && (lineY + iconH >= topY)) { int y2 = lineY + (cellHeight - iconH) / 2; ti.getIcon().paintIcon(this, g, 0, y2); lastLine = line - 1; // Paint only 1 icon // per line } } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } } else if (line < topLine) { break; // All other lines are above us, so quit now } } } } } } }