2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.diff.builtin.visualizer;
44
import java.awt.Dimension;
45
import java.awt.Rectangle;
46
import java.awt.Graphics;
47
import java.awt.Color;
50
import javax.accessibility.AccessibleContext;
51
import javax.accessibility.AccessibleRole;
53
import javax.swing.text.*;
54
import java.awt.FontMetrics;
55
import java.awt.Insets;
57
import org.openide.util.NbBundle;
59
import org.netbeans.editor.Coloring;
60
import org.netbeans.editor.EditorUI;
61
import org.netbeans.editor.FontMetricsCache;
62
import org.netbeans.editor.Settings;
63
import org.netbeans.editor.SettingsChangeEvent;
64
import org.netbeans.editor.SettingsChangeListener;
65
import org.netbeans.editor.SettingsDefaults;
66
import org.netbeans.editor.SettingsNames;
68
/** GlyphGutter is component for displaying line numbers and annotation
69
* glyph icons. Component also allow to "cycle" through the annotations. It
70
* means that if there is more than one annotation on the line, only one of them
71
* might be visible. And clicking the special cycling button in the gutter the user
72
* can cycle through the annotations.
74
* @author David Konecny
78
public class LinesComponent extends JComponent implements javax.accessibility.Accessible, SettingsChangeListener {
81
/** Document to which this gutter is attached*/
82
private JEditorPane editorPane;
84
/** Backroung color of the gutter */
85
private Color backgroundColor;
87
/** Foreground color of the gutter. Used for drawing line numbers. */
88
private Color foreColor;
90
/** Font used for drawing line numbers */
93
/** Height of the line as it was calculated in EditorUI. */
94
private int lineHeight = 1;
96
private float lineHeightCorrection = 1.0f;
98
/** Map holding the [name, coloring] pairs */
99
private Map coloringMap;
101
/** Flag whther the gutter was initialized or not. The painting is disabled till the
102
* gutter is not initialized */
103
private boolean init;
105
/** Width of the column used for drawing line numbers. The value contains
106
* also line number margins. */
107
private int numberWidth;
109
/** Whether the line numbers are shown or not */
110
private boolean showLineNumbers = true;
112
/** The gutter height is enlarged by number of lines which specifies this constant */
113
private static final int ENLARGE_GUTTER_HEIGHT = 300;
115
/** The hightest line number. This value is used for calculating width of the gutter */
116
private int highestLineNumber = 0;
118
/** Holds value of property lineNumberMargin. */
119
private Insets lineNumberMargin;
121
/** Holds value of property lineNumberDigitWidth. */
122
private int lineNumberDigitWidth;
124
/** Holds value of property lineAscent. */
125
private int lineAscent;
127
private LinkedList<String> linesList;
129
/** Holds value of property activeLine. */
130
private int activeLine = -1;
132
private static final long serialVersionUID = -4861542695772182147L;
134
public LinesComponent(JEditorPane pane) {
138
font = editorPane.getFont();
139
foreColor = editorPane.getForeground();
140
backgroundColor = editorPane.getBackground();
141
setLineNumberDigitWidth(10);
142
setLineNumberMargin(new Insets(2, 2, 2, 4));
143
Settings.addSettingsChangeListener(this); // Is added weakly.
147
/* Read accessible context
148
* @return - accessible context
150
public AccessibleContext getAccessibleContext () {
151
if (accessibleContext == null) {
152
accessibleContext = new AccessibleJComponent() {
153
public AccessibleRole getAccessibleRole() {
154
return AccessibleRole.PANEL;
158
return accessibleContext;
161
/** Do initialization of the glyph gutter*/
162
protected void init() {
164
getAccessibleContext().setAccessibleName(NbBundle.getMessage(LinesComponent.class, "ACSN_Lines_Component")); // NOI18N
165
getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(LinesComponent.class, "ACSD_Lines_Component")); // NOI18N
168
private void createLines() {
169
linesList = new LinkedList<String>();
171
StyledDocument doc = (StyledDocument)editorPane.getDocument();
172
int lastOffset = doc.getEndPosition().getOffset();
173
lineCnt = org.openide.text.NbDocument.findLineNumber(doc, lastOffset);
174
for (int i = 0; i < lineCnt; i++) {
175
linesList.add(Integer.toString(i + 1));
179
public void addEmptyLines(int line, int count) {
180
boolean appending = line > linesList.size();
181
for (int i = 0; i < count; i++) {
185
linesList.add(line, "");
191
* Insert line numbers. If at the end, then line numbers are added to the end of the component.
192
* If in the middle, subsequent lines are overwritten.
194
public void insertNumbers(int line, int startNum, int count) {
195
boolean appending = line >= linesList.size();
197
for (int i = 0; i < count; i++, startNum++) {
198
linesList.add(Integer.toString(startNum));
201
int toAdd = Math.max(line + count - linesList.size(), 0);
203
for (int i = 0; i < count; i++, startNum++, line++) {
204
linesList.set(line, Integer.toString(startNum));
206
for (int i = 0; i < toAdd; i++, startNum++) {
207
linesList.add(Integer.toString(startNum));
215
private void dumpResultLineNumbers() {
216
System.out.print("LinesComponent: linesList = ");
218
for (int i = 0; i < linesList.size(); i++) {
219
System.out.print(linesList.get(i)+", ");
221
System.out.println("");
226
* Remove line numbers and leave the corresponding part of the lines component empty.
227
* If at the end, then an empty space is added to the end of the component.
228
* If in the middle, subsequent lines are overwritten by an empty space.
230
public void removeNumbers(int line, int count) {
231
boolean appending = line >= linesList.size();
233
for (int i = 0; i < count; i++) {
237
int toAdd = Math.max(line + count - linesList.size(), 0);
239
for (int i = 0; i < count; i++, line++) {
240
linesList.set(line, "");
242
for (int i = 0; i < toAdd; i++) {
249
* Shrink the component, so that it will have <code>numLines</code> number of lines.
250
* @param numLines The new number of lines
252
public void shrink(int numLines) {
253
while (linesList.size() > numLines) {
254
linesList.remove(numLines);
258
/** Update colors, fonts, sizes and invalidate itself. This method is
259
* called from EditorUI.update() */
260
private void updateState(Graphics g) {
261
Class kitClass = editorPane.getEditorKit().getClass();
262
Object value = Settings.getValue(kitClass, SettingsNames.LINE_HEIGHT_CORRECTION);
263
//System.out.println("Line height correction = "+value);
264
if (!(value instanceof Float) || ((Float)value).floatValue() < 0) {
265
value = SettingsDefaults.defaultLineHeightCorrection;
267
lineHeightCorrection = ((Float)value).floatValue();
268
//System.out.println(" => correction = "+lineHeightCorrection);
269
Map cm = getColoringMap();
270
Object colValue = cm.get(SettingsNames.LINE_NUMBER_COLORING);
271
//System.out.println("Line number coloring = "+colValue);
273
if (colValue != null && colValue instanceof Coloring) {
274
col = (Coloring)colValue;
276
col = SettingsDefaults.defaultLineNumberColoring;
278
foreColor = col.getForeColor();
279
if (foreColor == null) {
280
foreColor = ((Coloring) cm.get(SettingsNames.DEFAULT_COLORING)).getForeColor();
282
backgroundColor = col.getBackColor();
283
if (backgroundColor == null) {
284
backgroundColor = ((Coloring) cm.get(SettingsNames.DEFAULT_COLORING)).getBackColor();
286
//System.out.println(" => foreground = "+foreColor+", background = "+backgroundColor);
288
font = col.getFont();
290
font = ((Coloring) cm.get(SettingsNames.DEFAULT_COLORING)).getFont();
292
FontMetrics fm = g.getFontMetrics(font);
297
maxHeight = Math.max(maxHeight, fm.getHeight());
298
maxAscent = Math.max(maxAscent, fm.getAscent());
301
// Apply lineHeightCorrection
302
lineHeight = (int)(maxHeight * lineHeightCorrection);
303
lineAscent = (int)(maxAscent * lineHeightCorrection);
306
//System.out.println("lineheight=" + lineHeight);//+", fm height = "+fm.getHeight());
307
//System.out.println("lineascent=" + lineAscent);//+", fm ascent = "+fm.getAscent());
308
showLineNumbers = true;
311
lineHeight = editorUI.getLineHeight();
312
lineAscent = editorUI.getLineAscent();
313
System.out.println("lineHeight = "+lineHeight);
314
System.out.println("lineascent=" + lineAscent);
316
showLineNumbers = editorUI.isLineNumberEnabled();
321
// initialize the value with current number of lines
322
if (highestLineNumber <= getLineCount()) {
323
highestLineNumber = getLineCount();
325
// System.out.println("highestLineNumber=" + highestLineNumber);
326
// width of a digit..
328
char[] digit = new char[1]; // will be used for '0' - '9'
329
for (int i = 0; i <= 9; i++) {
330
digit[0] = (char)('0' + i);
331
maxWidth = Math.max(maxWidth, fm.charsWidth(digit, 0, 1));
333
setLineNumberDigitWidth(maxWidth);
334
// System.out.println("maxwidth=" + maxWidth);
335
// System.out.println("numner of lines=" + highestLineNumber);
340
private void updateLineHeight(Graphics g) {
341
//System.err.println("EditorUI.updateLineHeight(): Computing lineHeight ...");
342
Map cm = getColoringMap();
343
Iterator i = cm.entrySet().iterator();
346
while (i.hasNext()) {
347
Map.Entry me = (Map.Entry)i.next();
348
String coloringName = (String)me.getKey();
349
Coloring c = (Coloring)me.getValue();
351
Font font = c.getFont();
352
if (font != null && (c.getFontMode() & Coloring.FONT_MODE_APPLY_SIZE) != 0) {
353
FontMetrics fm = g.getFontMetrics(font);
355
/*if (debugUpdateLineHeight) {
356
if (maxHeight < fm.getHeight()) {
357
System.err.println("Updating maxHeight from "
358
+ maxHeight + " to " + fm.getHeight()
359
+ ", coloringName=" + coloringName
364
if (maxHeight < fm.getHeight()) {
365
System.err.println("Updating maxAscent from "
366
+ maxAscent + " to " + fm.getAscent()
367
+ ", coloringName=" + coloringName
373
maxHeight = Math.max(maxHeight, fm.getHeight());
374
maxAscent = Math.max(maxAscent, fm.getAscent());
380
// Apply lineHeightCorrection
381
lineHeight = (int)(maxHeight * lineHeightCorrection);
382
lineAscent = (int)(maxAscent * lineHeightCorrection);
386
private Map getColoringMap() {
387
if (coloringMap == null) {
388
coloringMap = EditorUIHelper.getSharedColoringMapFor(editorPane.getEditorKit().getClass());
393
protected void resize() {
394
Dimension dim = new Dimension();
395
// System.out.println("resizing...................");
396
dim.width = getWidthDimension();
397
dim.height = getHeightDimension();
398
// enlarge the gutter so that inserting new lines into
399
// document does not cause resizing too often
400
dim.height += ENLARGE_GUTTER_HEIGHT * lineHeight;
402
numberWidth = getLineNumberWidth();
403
setPreferredSize(dim);
408
/** Return number of lines in the document */
409
protected int getLineCount() {
410
return linesList.size();
413
/** Gets number of digits in the number */
414
protected int getDigitCount(int number) {
415
return Integer.toString(number).length();
418
protected int getLineNumberWidth() {
420
Insets insets = getLineNumberMargin();
421
if (insets != null) {
422
newWidth += insets.left + insets.right;
424
newWidth += (getDigitCount(highestLineNumber) + 1) * getLineNumberDigitWidth();
425
// System.out.println("new width=" + newWidth);
429
protected int getWidthDimension() {
432
if (showLineNumbers) {
433
newWidth += getLineNumberWidth();
439
protected int getHeightDimension() {
440
return highestLineNumber * lineHeight /*TEMP+ (int)editorPane.getSize().getHeight() */;
443
/** Paint the gutter itself */
444
public void paintComponent(Graphics g) {
446
super.paintComponent(g);
452
Rectangle drawHere = g.getClipBounds();
454
// Fill clipping area with dirty brown/orange.
455
g.setColor(backgroundColor);
456
g.fillRect(drawHere.x, drawHere.y, drawHere.width, drawHere.height);
459
g.setColor(foreColor);
461
FontMetrics fm = FontMetricsCache.getFontMetrics(font, this);
463
Insets margin = getLineNumberMargin();
465
rightMargin = margin.right;
466
// calculate the first line which must be drawn
467
int line = (int)( (float)drawHere.y / (float)lineHeight );
471
// calculate the Y of the first line
472
int y = line * lineHeight;
474
if (showLineNumbers) {
475
int lastLine = (int)( (float)(drawHere.y+drawHere.height) / (float)lineHeight )+1;
476
if (lastLine > highestLineNumber) {
477
int prevHighest = highestLineNumber;
478
highestLineNumber = lastLine;
479
if (getDigitCount(highestLineNumber) > getDigitCount(prevHighest)) {
480
// System.out.println("resizing in paintComponent()");
481
// System.out.println("lastline=" + lastLine);
482
// System.out.println("highestLineNumber=" + highestLineNumber);
490
// draw liune numbers and annotations while we are in visible area
491
// "+(lineHeight/2)" means to don't draw less than half of the line number
492
while ( (y+(lineHeight/2)) <= (drawHere.y + drawHere.height) )
494
// draw line numbers if they are turned on
495
if (showLineNumbers) {
496
String lineStr = null;
497
if (line < linesList.size()) {
498
lineStr = linesList.get(line);
500
if (lineStr == null) {
503
String activeSymbol = "*";
504
int lineNumberWidth = fm.stringWidth(lineStr);
505
if (line == activeLine - 1) {
506
lineStr = lineStr + activeSymbol;
508
int activeSymbolWidth = fm.stringWidth(activeSymbol);
509
lineNumberWidth = lineNumberWidth + activeSymbolWidth;
510
g.drawString(lineStr, numberWidth-lineNumberWidth-rightMargin, y + getLineAscent());
518
/** Data for the line has changed and the line must be redraw. */
519
public void changedLine(int line) {
524
// redraw also lines around - three lines will be redrawn
527
int y = line * lineHeight;
529
repaint(0, y, (int)getSize().getWidth(), 3*lineHeight);
533
/** Repaint whole gutter.*/
534
public void changedAll() {
541
lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1;
542
} catch (BadLocationException e) {
551
protected void checkSize() {
552
int count = getLineCount();
553
if (count > highestLineNumber) {
554
highestLineNumber = count;
556
Dimension dim = getPreferredSize();
557
if (getWidthDimension() > dim.width ||
558
getHeightDimension() > dim.height) {
563
/** Getter for property lineNumberMargin.
564
* @return Value of property lineNumberMargin.
566
public Insets getLineNumberMargin() {
567
return this.lineNumberMargin;
570
/** Setter for property lineNumberMargin.
571
* @param lineNumberMargin New value of property lineNumberMargin.
573
public void setLineNumberMargin(Insets lineNumberMargin) {
574
this.lineNumberMargin = lineNumberMargin;
577
/** Getter for property lineNumberDigitWidth.
578
* @return Value of property lineNumberDigitWidth.
580
public int getLineNumberDigitWidth() {
581
return this.lineNumberDigitWidth;
584
/** Setter for property lineNumberDigitWidth.
585
* @param lineNumberDigitWidth New value of property lineNumberDigitWidth.
587
public void setLineNumberDigitWidth(int lineNumberDigitWidth) {
588
this.lineNumberDigitWidth = lineNumberDigitWidth;
591
/** Getter for property lineAscent.
592
* @return Value of property lineAscent.
594
public int getLineAscent() {
595
return this.lineAscent;
598
/** Setter for property lineAscent.
599
* @param lineAscent New value of property lineAscent.
601
public void setLineAscent(int lineAscent) {
602
this.lineAscent = lineAscent;
605
/** Getter for property activeLine.
606
* @return Value of property activeLine.
608
public int getActiveLine() {
609
return this.activeLine;
612
/** Setter for property activeLine.
613
* @param activeLine New value of property activeLine.
615
public void setActiveLine(int activeLine) {
616
this.activeLine = activeLine;
619
public void settingsChange(SettingsChangeEvent evt) {
625
private static class EditorUIHelper extends EditorUI {
626
public EditorUIHelper () {}
628
/** Gets the coloring map that can be shared by the components
629
* with the same kit. Only the component coloring map is provided.
631
public static Map getSharedColoringMapFor(Class kitClass) {
632
return EditorUIHelper.getSharedColoringMap(kitClass);