1 /* 2 * Copyright (c) 2008-2019 Emmanuel Dupuy. 3 * This project is distributed under the GPLv3 license. 4 * This is a Copyleft license that gives the user the right to use, 5 * copy and modify the code freely for non-commercial purposes. 6 */ 7 8 package org.jd.gui.view.component; 9 10 import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; 11 import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit; 12 import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaUI; 13 import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; 14 import org.fife.ui.rsyntaxtextarea.folding.Fold; 15 import org.fife.ui.rsyntaxtextarea.folding.FoldManager; 16 import org.fife.ui.rtextarea.Gutter; 17 import org.fife.ui.rtextarea.LineNumberList; 18 import org.fife.ui.rtextarea.RTextArea; 19 import org.fife.ui.rtextarea.RTextAreaUI; 20 21 import javax.swing.*; 22 import javax.swing.text.EditorKit; 23 import javax.swing.text.Element; 24 import javax.swing.text.JTextComponent; 25 import javax.swing.text.View; 26 import java.awt.*; 27 import java.util.Arrays; 28 import java.util.Map; 29 30 public abstract class CustomLineNumbersPage extends HyperlinkPage { 31 protected Color errorForeground = Color.RED; 32 protected boolean showMisalignment = true; 33 setErrorForeground(Color color)34 public void setErrorForeground(Color color) { 35 errorForeground = color; 36 } 37 setShowMisalignment(boolean b)38 public void setShowMisalignment(boolean b) { 39 showMisalignment = b; 40 } 41 42 /** 43 * Map[textarea line number] = original line number 44 */ 45 protected int[] lineNumberMap = null; 46 protected int maxLineNumber = 0; 47 setMaxLineNumber(int maxLineNumber)48 protected void setMaxLineNumber(int maxLineNumber) { 49 if (maxLineNumber > 0) { 50 if (lineNumberMap == null) { 51 lineNumberMap = new int[maxLineNumber+1]; 52 } else if (lineNumberMap.length <= maxLineNumber) { 53 int[] tmp = new int[maxLineNumber+1]; 54 System.arraycopy(lineNumberMap, 0, tmp, 0, lineNumberMap.length); 55 lineNumberMap = tmp; 56 } 57 58 this.maxLineNumber = maxLineNumber; 59 } 60 } 61 initLineNumbers()62 protected void initLineNumbers() { 63 String text = getText(); 64 int len = text.length(); 65 66 if (len == 0) { 67 setMaxLineNumber(0); 68 } else { 69 int mln = len - text.replace("\n", "").length(); 70 71 if (text.charAt(len-1) != '\n') { 72 mln++; 73 } 74 75 setMaxLineNumber(mln); 76 77 for (int i=1; i<=maxLineNumber; i++) { 78 lineNumberMap[i] = i; 79 } 80 } 81 } 82 setLineNumber(int textAreaLineNumber, int originalLineNumber)83 protected void setLineNumber(int textAreaLineNumber, int originalLineNumber) { 84 if (originalLineNumber > 0) { 85 setMaxLineNumber(textAreaLineNumber); 86 lineNumberMap[textAreaLineNumber] = originalLineNumber; 87 } 88 } 89 clearLineNumbers()90 protected void clearLineNumbers() { 91 if (lineNumberMap != null) { 92 Arrays.fill(lineNumberMap, 0); 93 } 94 } 95 getMaximumSourceLineNumber()96 protected int getMaximumSourceLineNumber() { return maxLineNumber; } 97 getTextAreaLineNumber(int originalLineNumber)98 protected int getTextAreaLineNumber(int originalLineNumber) { 99 int textAreaLineNumber = 1; 100 int greatestLowerSourceLineNumber = 0; 101 int i = lineNumberMap.length; 102 103 while (i-- > 0) { 104 int sln = lineNumberMap[i]; 105 if (sln <= originalLineNumber) { 106 if (greatestLowerSourceLineNumber < sln) { 107 greatestLowerSourceLineNumber = sln; 108 textAreaLineNumber = i; 109 } 110 } 111 } 112 113 return textAreaLineNumber; 114 } 115 newSyntaxTextArea()116 @Override protected RSyntaxTextArea newSyntaxTextArea() { return new SourceSyntaxTextArea(); } 117 118 public class SourceSyntaxTextArea extends HyperlinkSyntaxTextArea { createRTextAreaUI()119 @Override protected RTextAreaUI createRTextAreaUI() { return new SourceSyntaxTextAreaUI(this); } 120 } 121 122 /** 123 * A lot of code to replace the default LineNumberList... 124 */ 125 public class SourceSyntaxTextAreaUI extends RSyntaxTextAreaUI { SourceSyntaxTextAreaUI(JComponent rSyntaxTextArea)126 public SourceSyntaxTextAreaUI(JComponent rSyntaxTextArea) { super(rSyntaxTextArea); } getEditorKit(JTextComponent tc)127 @Override public EditorKit getEditorKit(JTextComponent tc) { return new SourceSyntaxTextAreaEditorKit(); } getVisibleEditorRect()128 @Override public Rectangle getVisibleEditorRect() { return super.getVisibleEditorRect(); } 129 } 130 131 public class SourceSyntaxTextAreaEditorKit extends RSyntaxTextAreaEditorKit { createLineNumberList(RTextArea textArea)132 @Override public LineNumberList createLineNumberList(RTextArea textArea) { return new SourceLineNumberList(textArea); } 133 } 134 135 /** 136 * Why 'LineNumberList' is so unexpandable ? Too many private fields & methods and too many package scope. 137 */ 138 public class SourceLineNumberList extends LineNumberList { 139 protected RTextArea rTextArea; 140 protected Map<?,?> aaHints; 141 protected Rectangle visibleRect; 142 protected Insets textAreaInsets; 143 protected Dimension preferredSize; 144 SourceLineNumberList(RTextArea textArea)145 public SourceLineNumberList(RTextArea textArea) { 146 super(textArea, null); 147 this.rTextArea = textArea; 148 } 149 150 @Override init()151 protected void init() { 152 super.init(); 153 visibleRect = new Rectangle(); 154 aaHints = RSyntaxUtilities.getDesktopAntiAliasHints(); 155 textAreaInsets = null; 156 } 157 158 /** 159 * @see org.fife.ui.rtextarea.LineNumberList#paintComponent(java.awt.Graphics) 160 */ 161 @Override paintComponent(Graphics g)162 protected void paintComponent(Graphics g) { 163 visibleRect = g.getClipBounds(visibleRect); 164 165 if (visibleRect == null) { 166 visibleRect = getVisibleRect(); 167 } 168 if (visibleRect == null) { 169 return; 170 } 171 172 int cellWidth = getPreferredSize().width; 173 int cellHeight = rTextArea.getLineHeight(); 174 int ascent = rTextArea.getMaxAscent(); 175 FoldManager fm = ((RSyntaxTextArea)rTextArea).getFoldManager(); 176 int RHS_BORDER_WIDTH = getRhsBorderWidth(); 177 FontMetrics metrics = g.getFontMetrics(); 178 int rhs = getWidth() - RHS_BORDER_WIDTH; 179 180 if (getParent() instanceof Gutter) { // Should always be true 181 g.setColor(getParent().getBackground()); 182 } else { 183 g.setColor(getBackground()); 184 } 185 186 g.fillRect(0, visibleRect.y, cellWidth, visibleRect.height); 187 g.setFont(getFont()); 188 189 if (aaHints != null) { 190 ((Graphics2D)g).addRenderingHints(aaHints); 191 } 192 193 if (rTextArea.getLineWrap()) { 194 SourceSyntaxTextAreaUI ui = (SourceSyntaxTextAreaUI)rTextArea.getUI(); 195 View v = ui.getRootView(rTextArea).getView(0); 196 Element root = rTextArea.getDocument().getDefaultRootElement(); 197 int lineCount = root.getElementCount(); 198 int topPosition = rTextArea.viewToModel(visibleRect.getLocation()); 199 int topLine = root.getElementIndex(topPosition); 200 Rectangle visibleEditorRect = ui.getVisibleEditorRect(); 201 Rectangle r = LineNumberList.getChildViewBounds(v, topLine, visibleEditorRect); 202 int y = r.y; 203 204 int visibleBottom = visibleRect.y + visibleRect.height; 205 206 // Keep painting lines until our y-coordinate is past the visible 207 // end of the text area. 208 209 while (y < visibleBottom) { 210 r = getChildViewBounds(v, topLine, visibleEditorRect); 211 212 // Paint the line number. 213 paintLineNumber(g, metrics, rhs, y+ascent, topLine + 1); 214 215 // The next possible y-coordinate is just after the last line 216 // painted. 217 y += r.height; 218 219 // Update topLine (we're actually using it for our "current line" 220 // variable now). 221 if (fm != null) { 222 Fold fold = fm.getFoldForLine(topLine); 223 if ((fold != null) && fold.isCollapsed()) { 224 topLine += fold.getCollapsedLineCount(); 225 } 226 } 227 228 if (++topLine >= lineCount) { 229 break; 230 } 231 } 232 } else { 233 textAreaInsets = rTextArea.getInsets(textAreaInsets); 234 235 if (visibleRect.y < textAreaInsets.top) { 236 visibleRect.height -= (textAreaInsets.top - visibleRect.y); 237 visibleRect.y = textAreaInsets.top; 238 } 239 240 int topLine = (visibleRect.y - textAreaInsets.top) / cellHeight; 241 int actualTopY = topLine * cellHeight + textAreaInsets.top; 242 int y = actualTopY + ascent; 243 244 // Get the actual first line to paint, taking into account folding. 245 topLine += fm.getHiddenLineCountAbove(topLine, true); 246 247 // Paint line numbers 248 g.setColor(getForeground()); 249 250 int line = topLine + 1; 251 252 while ((y < visibleRect.y + visibleRect.height + ascent) && (line <= rTextArea.getLineCount())) { 253 paintLineNumber(g, metrics, rhs, y, line); 254 255 y += cellHeight; 256 257 if (fm != null) { 258 Fold fold = fm.getFoldForLine(line - 1); 259 // Skip to next line to paint, taking extra care for lines with 260 // block ends and begins together, e.g. "} else {" 261 while ((fold != null) && fold.isCollapsed()) { 262 int hiddenLineCount = fold.getLineCount(); 263 if (hiddenLineCount == 0) { 264 // Fold parser identified a 0-line fold region... This 265 // is really a bug, but we'll handle it gracefully. 266 break; 267 } 268 line += hiddenLineCount; 269 fold = fm.getFoldForLine(line - 1); 270 } 271 } 272 273 line++; 274 } 275 } 276 } 277 paintLineNumber(Graphics g, FontMetrics metrics, int x, int y, int lineNumber)278 protected void paintLineNumber(Graphics g, FontMetrics metrics, int x, int y, int lineNumber) { 279 int originalLineNumber; 280 281 if (lineNumberMap != null) { 282 originalLineNumber = (lineNumber < lineNumberMap.length) ? lineNumberMap[lineNumber] : 0; 283 } else { 284 originalLineNumber = lineNumber; 285 } 286 287 if (originalLineNumber != 0) { 288 String number = Integer.toString(originalLineNumber); 289 int strWidth = metrics.stringWidth(number); 290 g.setColor(showMisalignment && (lineNumber != originalLineNumber) ? errorForeground : getForeground()); 291 g.drawString(number, x-strWidth, y); 292 } 293 } 294 getRhsBorderWidth()295 public int getRhsBorderWidth() { return ((RSyntaxTextArea)rTextArea).isCodeFoldingEnabled() ? 0 : 4; } 296 297 @Override getPreferredSize()298 public Dimension getPreferredSize() { 299 if (preferredSize == null) { 300 int lineCount = getMaximumSourceLineNumber(); 301 302 if (lineCount > 0) { 303 Font font = getFont(); 304 FontMetrics fontMetrics = getFontMetrics(font); 305 int count = 1; 306 307 while (lineCount >= 10) { 308 lineCount = lineCount / 10; 309 count++; 310 } 311 312 int preferredWidth = fontMetrics.charWidth('9') * count + 10; 313 preferredSize = new Dimension(preferredWidth, 0); 314 } else { 315 preferredSize = new Dimension(0, 0); 316 } 317 } 318 319 return preferredSize; 320 } 321 } 322 } 323