001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.diff.builtin.visualizer;
043:
044: import java.awt.Dimension;
045: import java.awt.Rectangle;
046: import java.awt.Graphics;
047: import java.awt.Color;
048: import java.awt.Font;
049: import java.util.*;
050: import javax.accessibility.AccessibleContext;
051: import javax.accessibility.AccessibleRole;
052: import javax.swing.*;
053: import javax.swing.text.*;
054: import java.awt.FontMetrics;
055: import java.awt.Insets;
056:
057: import org.openide.util.NbBundle;
058:
059: import org.netbeans.editor.Coloring;
060: import org.netbeans.editor.EditorUI;
061: import org.netbeans.editor.FontMetricsCache;
062: import org.netbeans.editor.Settings;
063: import org.netbeans.editor.SettingsChangeEvent;
064: import org.netbeans.editor.SettingsChangeListener;
065: import org.netbeans.editor.SettingsDefaults;
066: import org.netbeans.editor.SettingsNames;
067:
068: /** GlyphGutter is component for displaying line numbers and annotation
069: * glyph icons. Component also allow to "cycle" through the annotations. It
070: * means that if there is more than one annotation on the line, only one of them
071: * might be visible. And clicking the special cycling button in the gutter the user
072: * can cycle through the annotations.
073: *
074: * @author David Konecny
075: * @since 07/2001
076: */
077:
078: public class LinesComponent extends JComponent implements
079: javax.accessibility.Accessible, SettingsChangeListener {
080:
081: /** Document to which this gutter is attached*/
082: private JEditorPane editorPane;
083:
084: /** Backroung color of the gutter */
085: private Color backgroundColor;
086:
087: /** Foreground color of the gutter. Used for drawing line numbers. */
088: private Color foreColor;
089:
090: /** Font used for drawing line numbers */
091: private Font font;
092:
093: /** Height of the line as it was calculated in EditorUI. */
094: private int lineHeight = 1;
095:
096: private float lineHeightCorrection = 1.0f;
097:
098: /** Map holding the [name, coloring] pairs */
099: private Map coloringMap;
100:
101: /** Flag whther the gutter was initialized or not. The painting is disabled till the
102: * gutter is not initialized */
103: private boolean init;
104:
105: /** Width of the column used for drawing line numbers. The value contains
106: * also line number margins. */
107: private int numberWidth;
108:
109: /** Whether the line numbers are shown or not */
110: private boolean showLineNumbers = true;
111:
112: /** The gutter height is enlarged by number of lines which specifies this constant */
113: private static final int ENLARGE_GUTTER_HEIGHT = 300;
114:
115: /** The hightest line number. This value is used for calculating width of the gutter */
116: private int highestLineNumber = 0;
117:
118: /** Holds value of property lineNumberMargin. */
119: private Insets lineNumberMargin;
120:
121: /** Holds value of property lineNumberDigitWidth. */
122: private int lineNumberDigitWidth;
123:
124: /** Holds value of property lineAscent. */
125: private int lineAscent;
126:
127: private LinkedList<String> linesList;
128:
129: /** Holds value of property activeLine. */
130: private int activeLine = -1;
131:
132: private static final long serialVersionUID = -4861542695772182147L;
133:
134: public LinesComponent(JEditorPane pane) {
135: super ();
136: init = false;
137: editorPane = 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.
144: init();
145: }
146:
147: /* Read accessible context
148: * @return - accessible context
149: */
150: public AccessibleContext getAccessibleContext() {
151: if (accessibleContext == null) {
152: accessibleContext = new AccessibleJComponent() {
153: public AccessibleRole getAccessibleRole() {
154: return AccessibleRole.PANEL;
155: }
156: };
157: }
158: return accessibleContext;
159: }
160:
161: /** Do initialization of the glyph gutter*/
162: protected void init() {
163: createLines();
164: getAccessibleContext().setAccessibleName(
165: NbBundle.getMessage(LinesComponent.class,
166: "ACSN_Lines_Component")); // NOI18N
167: getAccessibleContext().setAccessibleDescription(
168: NbBundle.getMessage(LinesComponent.class,
169: "ACSD_Lines_Component")); // NOI18N
170: }
171:
172: private void createLines() {
173: linesList = new LinkedList<String>();
174: int lineCnt;
175: StyledDocument doc = (StyledDocument) editorPane.getDocument();
176: int lastOffset = doc.getEndPosition().getOffset();
177: lineCnt = org.openide.text.NbDocument.findLineNumber(doc,
178: lastOffset);
179: for (int i = 0; i < lineCnt; i++) {
180: linesList.add(Integer.toString(i + 1));
181: }
182: }
183:
184: public void addEmptyLines(int line, int count) {
185: boolean appending = line > linesList.size();
186: for (int i = 0; i < count; i++) {
187: if (appending) {
188: linesList.add("");
189: } else {
190: linesList.add(line, "");
191: }
192: }
193: }
194:
195: /**
196: * Insert line numbers. If at the end, then line numbers are added to the end of the component.
197: * If in the middle, subsequent lines are overwritten.
198: */
199: public void insertNumbers(int line, int startNum, int count) {
200: boolean appending = line >= linesList.size();
201: if (appending) {
202: for (int i = 0; i < count; i++, startNum++) {
203: linesList.add(Integer.toString(startNum));
204: }
205: } else {
206: int toAdd = Math.max(line + count - linesList.size(), 0);
207: count -= toAdd;
208: for (int i = 0; i < count; i++, startNum++, line++) {
209: linesList.set(line, Integer.toString(startNum));
210: }
211: for (int i = 0; i < toAdd; i++, startNum++) {
212: linesList.add(Integer.toString(startNum));
213: }
214: }
215: }
216:
217: /*
218: * Test method.
219: *
220: private void dumpResultLineNumbers() {
221: System.out.print("LinesComponent: linesList = ");
222: boolean was = false;
223: for (int i = 0; i < linesList.size(); i++) {
224: System.out.print(linesList.get(i)+", ");
225: }
226: System.out.println("");
227: }
228: */
229:
230: /**
231: * Remove line numbers and leave the corresponding part of the lines component empty.
232: * If at the end, then an empty space is added to the end of the component.
233: * If in the middle, subsequent lines are overwritten by an empty space.
234: */
235: public void removeNumbers(int line, int count) {
236: boolean appending = line >= linesList.size();
237: if (appending) {
238: for (int i = 0; i < count; i++) {
239: linesList.add("");
240: }
241: } else {
242: int toAdd = Math.max(line + count - linesList.size(), 0);
243: count -= toAdd;
244: for (int i = 0; i < count; i++, line++) {
245: linesList.set(line, "");
246: }
247: for (int i = 0; i < toAdd; i++) {
248: linesList.add("");
249: }
250: }
251: }
252:
253: /**
254: * Shrink the component, so that it will have <code>numLines</code> number of lines.
255: * @param numLines The new number of lines
256: */
257: public void shrink(int numLines) {
258: while (linesList.size() > numLines) {
259: linesList.remove(numLines);
260: }
261: }
262:
263: /** Update colors, fonts, sizes and invalidate itself. This method is
264: * called from EditorUI.update() */
265: private void updateState(Graphics g) {
266: Class kitClass = editorPane.getEditorKit().getClass();
267: Object value = Settings.getValue(kitClass,
268: SettingsNames.LINE_HEIGHT_CORRECTION);
269: //System.out.println("Line height correction = "+value);
270: if (!(value instanceof Float)
271: || ((Float) value).floatValue() < 0) {
272: value = SettingsDefaults.defaultLineHeightCorrection;
273: }
274: lineHeightCorrection = ((Float) value).floatValue();
275: //System.out.println(" => correction = "+lineHeightCorrection);
276: Map cm = getColoringMap();
277: Object colValue = cm.get(SettingsNames.LINE_NUMBER_COLORING);
278: //System.out.println("Line number coloring = "+colValue);
279: Coloring col = null;
280: if (colValue != null && colValue instanceof Coloring) {
281: col = (Coloring) colValue;
282: } else {
283: col = SettingsDefaults.defaultLineNumberColoring;
284: }
285: foreColor = col.getForeColor();
286: if (foreColor == null) {
287: foreColor = ((Coloring) cm
288: .get(SettingsNames.DEFAULT_COLORING))
289: .getForeColor();
290: }
291: backgroundColor = col.getBackColor();
292: if (backgroundColor == null) {
293: backgroundColor = ((Coloring) cm
294: .get(SettingsNames.DEFAULT_COLORING))
295: .getBackColor();
296: }
297: //System.out.println(" => foreground = "+foreColor+", background = "+backgroundColor);
298:
299: font = col.getFont();
300: if (font == null) {
301: font = ((Coloring) cm.get(SettingsNames.DEFAULT_COLORING))
302: .getFont();
303: }
304: FontMetrics fm = g.getFontMetrics(font);
305: /*
306: int maxHeight = 1;
307: int maxAscent = 0;
308: if (fm != null) {
309: maxHeight = Math.max(maxHeight, fm.getHeight());
310: maxAscent = Math.max(maxAscent, fm.getAscent());
311: }
312:
313: // Apply lineHeightCorrection
314: lineHeight = (int)(maxHeight * lineHeightCorrection);
315: lineAscent = (int)(maxAscent * lineHeightCorrection);
316: */
317: updateLineHeight(g);
318: //System.out.println("lineheight=" + lineHeight);//+", fm height = "+fm.getHeight());
319: //System.out.println("lineascent=" + lineAscent);//+", fm ascent = "+fm.getAscent());
320: showLineNumbers = true;
321:
322: /*
323: lineHeight = editorUI.getLineHeight();
324: lineAscent = editorUI.getLineAscent();
325: System.out.println("lineHeight = "+lineHeight);
326: System.out.println("lineascent=" + lineAscent);
327:
328: showLineNumbers = editorUI.isLineNumberEnabled();
329: */
330:
331: init = true;
332:
333: // initialize the value with current number of lines
334: if (highestLineNumber <= getLineCount()) {
335: highestLineNumber = getLineCount();
336: }
337: // System.out.println("highestLineNumber=" + highestLineNumber);
338: // width of a digit..
339: int maxWidth = 1;
340: char[] digit = new char[1]; // will be used for '0' - '9'
341: for (int i = 0; i <= 9; i++) {
342: digit[0] = (char) ('0' + i);
343: maxWidth = Math.max(maxWidth, fm.charsWidth(digit, 0, 1));
344: }
345: setLineNumberDigitWidth(maxWidth);
346: // System.out.println("maxwidth=" + maxWidth);
347: // System.out.println("numner of lines=" + highestLineNumber);
348:
349: resize();
350: }
351:
352: private void updateLineHeight(Graphics g) {
353: //System.err.println("EditorUI.updateLineHeight(): Computing lineHeight ...");
354: Map cm = getColoringMap();
355: Iterator i = cm.entrySet().iterator();
356: int maxHeight = 1;
357: int maxAscent = 0;
358: while (i.hasNext()) {
359: Map.Entry me = (Map.Entry) i.next();
360: String coloringName = (String) me.getKey();
361: Coloring c = (Coloring) me.getValue();
362: if (c != null) {
363: Font font = c.getFont();
364: if (font != null
365: && (c.getFontMode() & Coloring.FONT_MODE_APPLY_SIZE) != 0) {
366: FontMetrics fm = g.getFontMetrics(font);
367: if (fm != null) {
368: /*if (debugUpdateLineHeight) {
369: if (maxHeight < fm.getHeight()) {
370: System.err.println("Updating maxHeight from "
371: + maxHeight + " to " + fm.getHeight()
372: + ", coloringName=" + coloringName
373: + ", font=" + font
374: );
375: }
376:
377: if (maxHeight < fm.getHeight()) {
378: System.err.println("Updating maxAscent from "
379: + maxAscent + " to " + fm.getAscent()
380: + ", coloringName=" + coloringName
381: + ", font=" + font
382: );
383: }
384: }
385: */
386: maxHeight = Math.max(maxHeight, fm.getHeight());
387: maxAscent = Math.max(maxAscent, fm.getAscent());
388: }
389: }
390: }
391: }
392:
393: // Apply lineHeightCorrection
394: lineHeight = (int) (maxHeight * lineHeightCorrection);
395: lineAscent = (int) (maxAscent * lineHeightCorrection);
396:
397: }
398:
399: private Map getColoringMap() {
400: if (coloringMap == null) {
401: coloringMap = EditorUIHelper
402: .getSharedColoringMapFor(editorPane.getEditorKit()
403: .getClass());
404: }
405: return coloringMap;
406: }
407:
408: protected void resize() {
409: Dimension dim = new Dimension();
410: // System.out.println("resizing...................");
411: dim.width = getWidthDimension();
412: dim.height = getHeightDimension();
413: // enlarge the gutter so that inserting new lines into
414: // document does not cause resizing too often
415: dim.height += ENLARGE_GUTTER_HEIGHT * lineHeight;
416:
417: numberWidth = getLineNumberWidth();
418: setPreferredSize(dim);
419:
420: revalidate();
421: }
422:
423: /** Return number of lines in the document */
424: protected int getLineCount() {
425: return linesList.size();
426: }
427:
428: /** Gets number of digits in the number */
429: protected int getDigitCount(int number) {
430: return Integer.toString(number).length();
431: }
432:
433: protected int getLineNumberWidth() {
434: int newWidth = 0;
435: Insets insets = getLineNumberMargin();
436: if (insets != null) {
437: newWidth += insets.left + insets.right;
438: }
439: newWidth += (getDigitCount(highestLineNumber) + 1)
440: * getLineNumberDigitWidth();
441: // System.out.println("new width=" + newWidth);
442: return newWidth;
443: }
444:
445: protected int getWidthDimension() {
446: int newWidth = 0;
447:
448: if (showLineNumbers) {
449: newWidth += getLineNumberWidth();
450: }
451:
452: return newWidth;
453: }
454:
455: protected int getHeightDimension() {
456: return highestLineNumber * lineHeight /*TEMP+ (int)editorPane.getSize().getHeight() */;
457: }
458:
459: /** Paint the gutter itself */
460: public void paintComponent(Graphics g) {
461:
462: super .paintComponent(g);
463: if (!init) {
464: updateState(g);
465: }
466: // return;
467:
468: Rectangle drawHere = g.getClipBounds();
469:
470: // Fill clipping area with dirty brown/orange.
471: g.setColor(backgroundColor);
472: g.fillRect(drawHere.x, drawHere.y, drawHere.width,
473: drawHere.height);
474:
475: g.setFont(font);
476: g.setColor(foreColor);
477:
478: FontMetrics fm = FontMetricsCache.getFontMetrics(font, this );
479: int rightMargin = 0;
480: Insets margin = getLineNumberMargin();
481: if (margin != null)
482: rightMargin = margin.right;
483: // calculate the first line which must be drawn
484: int line = (int) ((float) drawHere.y / (float) lineHeight);
485: if (line > 0)
486: line--;
487:
488: // calculate the Y of the first line
489: int y = line * lineHeight;
490:
491: if (showLineNumbers) {
492: int lastLine = (int) ((float) (drawHere.y + drawHere.height) / (float) lineHeight) + 1;
493: if (lastLine > highestLineNumber) {
494: int prevHighest = highestLineNumber;
495: highestLineNumber = lastLine;
496: if (getDigitCount(highestLineNumber) > getDigitCount(prevHighest)) {
497: // System.out.println("resizing in paintComponent()");
498: // System.out.println("lastline=" + lastLine);
499: // System.out.println("highestLineNumber=" + highestLineNumber);
500: resize();
501: return;
502: }
503: }
504: }
505:
506: // draw liune numbers and annotations while we are in visible area
507: // "+(lineHeight/2)" means to don't draw less than half of the line number
508: while ((y + (lineHeight / 2)) <= (drawHere.y + drawHere.height)) {
509: // draw line numbers if they are turned on
510: if (showLineNumbers) {
511: String lineStr = null;
512: if (line < linesList.size()) {
513: lineStr = linesList.get(line);
514: }
515: if (lineStr == null) {
516: lineStr = "";
517: }
518: String activeSymbol = "*";
519: int lineNumberWidth = fm.stringWidth(lineStr);
520: if (line == activeLine - 1) {
521: lineStr = lineStr + activeSymbol;
522: }
523: int activeSymbolWidth = fm.stringWidth(activeSymbol);
524: lineNumberWidth = lineNumberWidth + activeSymbolWidth;
525: g.drawString(lineStr, numberWidth - lineNumberWidth
526: - rightMargin, y + getLineAscent());
527: }
528:
529: y += lineHeight;
530: line++;
531: }
532: }
533:
534: /** Data for the line has changed and the line must be redraw. */
535: public void changedLine(int line) {
536:
537: if (!init)
538: return;
539:
540: // redraw also lines around - three lines will be redrawn
541: if (line > 0)
542: line--;
543: int y = line * lineHeight;
544:
545: repaint(0, y, (int) getSize().getWidth(), 3 * lineHeight);
546: checkSize();
547: }
548:
549: /** Repaint whole gutter.*/
550: public void changedAll() {
551:
552: if (!init)
553: return;
554:
555: /* int lineCnt;
556: try {
557: lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1;
558: } catch (BadLocationException e) {
559: lineCnt = 1;
560: }
561: */
562:
563: repaint();
564: checkSize();
565: }
566:
567: protected void checkSize() {
568: int count = getLineCount();
569: if (count > highestLineNumber) {
570: highestLineNumber = count;
571: }
572: Dimension dim = getPreferredSize();
573: if (getWidthDimension() > dim.width
574: || getHeightDimension() > dim.height) {
575: resize();
576: }
577: }
578:
579: /** Getter for property lineNumberMargin.
580: * @return Value of property lineNumberMargin.
581: */
582: public Insets getLineNumberMargin() {
583: return this .lineNumberMargin;
584: }
585:
586: /** Setter for property lineNumberMargin.
587: * @param lineNumberMargin New value of property lineNumberMargin.
588: */
589: public void setLineNumberMargin(Insets lineNumberMargin) {
590: this .lineNumberMargin = lineNumberMargin;
591: }
592:
593: /** Getter for property lineNumberDigitWidth.
594: * @return Value of property lineNumberDigitWidth.
595: */
596: public int getLineNumberDigitWidth() {
597: return this .lineNumberDigitWidth;
598: }
599:
600: /** Setter for property lineNumberDigitWidth.
601: * @param lineNumberDigitWidth New value of property lineNumberDigitWidth.
602: */
603: public void setLineNumberDigitWidth(int lineNumberDigitWidth) {
604: this .lineNumberDigitWidth = lineNumberDigitWidth;
605: }
606:
607: /** Getter for property lineAscent.
608: * @return Value of property lineAscent.
609: */
610: public int getLineAscent() {
611: return this .lineAscent;
612: }
613:
614: /** Setter for property lineAscent.
615: * @param lineAscent New value of property lineAscent.
616: */
617: public void setLineAscent(int lineAscent) {
618: this .lineAscent = lineAscent;
619: }
620:
621: /** Getter for property activeLine.
622: * @return Value of property activeLine.
623: */
624: public int getActiveLine() {
625: return this .activeLine;
626: }
627:
628: /** Setter for property activeLine.
629: * @param activeLine New value of property activeLine.
630: */
631: public void setActiveLine(int activeLine) {
632: this .activeLine = activeLine;
633: }
634:
635: public void settingsChange(SettingsChangeEvent evt) {
636: coloringMap = null;
637: init = false;
638: repaint();
639: }
640:
641: private static class EditorUIHelper extends EditorUI {
642: public EditorUIHelper() {
643: }
644:
645: /** Gets the coloring map that can be shared by the components
646: * with the same kit. Only the component coloring map is provided.
647: */
648: public static Map getSharedColoringMapFor(Class kitClass) {
649: return EditorUIHelper.getSharedColoringMap(kitClass);
650: }
651:
652: }
653:
654: }
|