/* * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License * at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jsyntaxpane.components; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Logger; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; import jsyntaxpane.SyntaxDocument; import jsyntaxpane.actions.GotoLineDialog; import jsyntaxpane.actions.ActionUtils; import jsyntaxpane.util.Configuration; /** * LineRuleis used to number the lines in the EdiorPane * @author Ayman Al-Sairafi */ public class LineNumbersRuler extends JComponent implements SyntaxComponent, PropertyChangeListener, DocumentListener { public static final String PROPERTY_BACKGROUND = "LineNumbers.Background"; public static final String PROPERTY_FOREGROUND = "LineNumbers.Foreground"; public static final String PROPERTY_LEFT_MARGIN = "LineNumbers.LeftMargin"; public static final String PROPERTY_RIGHT_MARGIN = "LineNumbers.RightMargin"; public static final int DEFAULT_R_MARGIN = 5; public static final int DEFAULT_L_MARGIN = 5; private JEditorPane pane; private String format; private int lineCount = -1; private int r_margin; private int l_margin; private int charHeight; private int charWidth; private GotoLineDialog gotoLineDialog = null; private MouseListener mouseListener = null; /** * The status is used to have proper propertyCHange support. We need to know if we are INSTALLING * the component or DE-INSTALLING it */ static enum Status { INSTALLING, DEINSTALLING } private Status status; public LineNumbersRuler() { super(); } @Override protected void paintComponent(Graphics g) { g.setFont(pane.getFont()); Rectangle clip = g.getClipBounds(); g.setColor(getBackground()); g.fillRect(clip.x, clip.y, clip.width, clip.height); g.setColor(getForeground()); int lh = charHeight; int end = clip.y + clip.height + lh; int lineNum = clip.y / lh + 1; // round the start to a multiple of lh, and shift by 2 pixels to align // properly to the text. for (int y = (clip.y / lh) * lh + lh - 2; y <= end; y += lh) { String text = String.format(format, lineNum); g.drawString(text, l_margin, y); lineNum++; if (lineNum > lineCount) { break; } } } /** * Update the size of the line numbers based on the length of the document */ private void updateSize() { int newLineCount = ActionUtils.getLineCount(pane); if (newLineCount == lineCount) { return; } lineCount = newLineCount; int h = lineCount * charHeight + pane.getHeight(); int d = (int) Math.log10(lineCount) + 1; if (d < 1) { d = 1; } int w = d * charWidth + r_margin + l_margin; format = "%" + d + "d"; setPreferredSize(new Dimension(w, h)); if(getParent() != null){ getParent().doLayout(); } } /** * Get the JscrollPane that contains this EditorPane, or null if no * JScrollPane is the parent of this editor * @param editorPane * @return */ public JScrollPane getScrollPane(JTextComponent editorPane) { Container p = editorPane.getParent(); while (p != null) { if (p instanceof JScrollPane) { return (JScrollPane) p; } p = p.getParent(); } return null; } public void config(Configuration config, String prefix) { r_margin = config.getPrefixInteger(prefix, PROPERTY_RIGHT_MARGIN, DEFAULT_R_MARGIN); l_margin = config.getPrefixInteger(prefix, PROPERTY_LEFT_MARGIN, DEFAULT_L_MARGIN); Color foreground = config.getPrefixColor(prefix, PROPERTY_FOREGROUND, Color.BLACK); setForeground(foreground); Color back = config.getPrefixColor(prefix, PROPERTY_BACKGROUND, Color.WHITE); setBackground(back); } public void install(JEditorPane editor) { this.pane = editor; charHeight = pane.getFontMetrics(pane.getFont()).getHeight(); charWidth = pane.getFontMetrics(pane.getFont()).charWidth('0'); editor.addPropertyChangeListener(this); JScrollPane sp = getScrollPane(pane); if (sp == null) { Logger.getLogger(this.getClass().getName()).warning( "JEditorPane is not enclosed in JScrollPane, " + "no LineNumbers will be displayed"); } else { sp.setRowHeaderView(this); this.pane.getDocument().addDocumentListener(this); updateSize(); gotoLineDialog = new GotoLineDialog(pane); mouseListener = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { gotoLineDialog.setVisible(true); } }; addMouseListener(mouseListener); } status = Status.INSTALLING; } public void deinstall(JEditorPane editor) { removeMouseListener(mouseListener); status = Status.DEINSTALLING; JScrollPane sp = getScrollPane(editor); if (sp != null) { editor.getDocument().removeDocumentListener(this); sp.setRowHeaderView(null); } } public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("document")) { if (evt.getOldValue() instanceof SyntaxDocument) { SyntaxDocument syntaxDocument = (SyntaxDocument) evt.getOldValue(); syntaxDocument.removeDocumentListener(this); } if (evt.getNewValue() instanceof SyntaxDocument && status.equals(Status.INSTALLING)) { SyntaxDocument syntaxDocument = (SyntaxDocument) evt.getNewValue(); syntaxDocument.addDocumentListener(this); } } else if (evt.getPropertyName().equals("font")) { charHeight = pane.getFontMetrics(pane.getFont()).getHeight(); charWidth = pane.getFontMetrics(pane.getFont()).charWidth('0'); } } public void insertUpdate(DocumentEvent e) { updateSize(); } public void removeUpdate(DocumentEvent e) { updateSize(); } public void changedUpdate(DocumentEvent e) { updateSize(); } }