/******************************************************************************* * Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html * * Contributors: * Matthew Reeder * - Initial API and implementation *******************************************************************************/ package net.sf.robocode.ui.editor; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import static java.lang.Math.max; import static java.lang.Math.min; /** * Custom widget for line numbers, meant to be the row header for a JScrollPane * that scrolls a JEditorPane. * * @author Matthew Reeder (original) */ @SuppressWarnings("serial") public class LineNumbers extends JComponent implements DocumentListener, MouseListener, MouseMotionListener, CaretListener { private final JEditorPane editorPane; private int currentLines, lineWidth, anchor, lastIndex, offset, textWidth; public LineNumbers(JEditorPane editorPane) { this.editorPane = editorPane; editorPane.getDocument().addDocumentListener(this); setForeground(editorPane.getForeground()); setBackground(editorPane.getBackground()); currentLines = 1; lastIndex = -1; anchor = -1; addMouseListener(this); addMouseMotionListener(this); editorPane.addMouseListener(this); setPreferredSize(new Dimension(24, 17)); editorPane.addCaretListener(this); } /** * Listens for changes on the Document in its associated text pane. * <p/> * If the number of lines has changed, it updates its view. */ public void changedUpdate(DocumentEvent e) { anchor = lastIndex = -1; try { checkLines(e.getDocument().getText(0, e.getDocument().getLength())); } catch (BadLocationException ex) { ex.printStackTrace(); } } /** * Listens for changes on the Document in its associated text pane. * <p/> * If the number of lines has changed, it updates its view. */ public void insertUpdate(DocumentEvent e) { anchor = lastIndex = -1; try { checkLines(e.getDocument().getText(0, e.getDocument().getLength())); } catch (BadLocationException ex) { ex.printStackTrace(); } } /** * Listens for changes on the Document in its associated text pane. * <p/> * If the number of lines has changed, it updates its view. */ public void removeUpdate(DocumentEvent e) { anchor = lastIndex = -1; try { checkLines(e.getDocument().getText(0, e.getDocument().getLength())); } catch (BadLocationException ex) { ex.printStackTrace(); } } /** * Called by the DocumentListener methods to check if the number of lines * has changed, and if it has, it updates the display. * * @param text the text to compare to the current text */ protected void checkLines(String text) { int lines = 0; int index = -1; do { lines++; index = text.indexOf('\n', index + 1); } while (index >= 0); if (lines != currentLines) { currentLines = lines; repaint(); } } /** * Draws the line numbers. */ @Override public void paint(Graphics g) { checkLines(editorPane.getText()); g.setFont(editorPane.getFont()); FontMetrics fm = g.getFontMetrics(); // note: using font metrics for the editor font, using a bold font :-) if (lineWidth == 0) { lineWidth = fm.getHeight(); offset = fm.getAscent() - lineWidth; } g.setFont(editorPane.getFont().deriveFont(Font.BOLD)); g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(editorPane.getSelectionColor()); int start = max(1, min(anchor, lastIndex)); int end = min(currentLines, max(anchor, lastIndex)); g.fillRect(0, (start - 1) * lineWidth - offset, textWidth, (end - start + 1) * lineWidth); int maxwidth = max(textWidth, fm.stringWidth("000")); for (int i = 1; i <= currentLines; i++) { String str = Integer.toString(i); maxwidth = max(maxwidth, fm.stringWidth(str)); if (i >= start && i <= end) { g.setColor(editorPane.getSelectedTextColor()); } else { g.setColor(getForeground()); } g.drawString(str, textWidth - fm.stringWidth(str), i * lineWidth + offset); } textWidth = maxwidth; g.setColor(getForeground()); g.drawLine(maxwidth + 2, 0, maxwidth + 2, getHeight()); g.drawLine(maxwidth + 1, 0, maxwidth + 1, getHeight()); Dimension dim = getPreferredSize(); if (dim.height != lineWidth * currentLines || dim.width != maxwidth + 8) { setPreferredSize(new Dimension(maxwidth + 8, lineWidth * currentLines)); setMinimumSize(new Dimension(maxwidth + 8, lineWidth * currentLines)); repaint(); } } /** * Handles selection in the text pane due to gestures on the line numbers. */ protected void doSelection() { int first = min(anchor, lastIndex); int last = max(anchor, lastIndex); try { String text = editorPane.getDocument().getText(0, editorPane.getDocument().getLength()); int index = -1, lines = 1; while (lines < first) { index = text.indexOf('\n', index + 1); lines++; } int firstindex = index + 1; do { index = text.indexOf('\n', index + 1); lines++; } while (lines <= last && index > 0); int lastindex; if (index < 0) { lastindex = editorPane.getDocument().getLength(); } else { lastindex = index + 1; } editorPane.setSelectionStart(firstindex); editorPane.setSelectionEnd(lastindex); } catch (BadLocationException ignored) {} } /** * Selects the number that was clicked on and sets it as an "anchor" for * dragging. */ public void mousePressed(MouseEvent e) { if (e.getSource() == this) { if (e.getX() < textWidth) { anchor = e.getY() / lineWidth + 1; lastIndex = anchor; doSelection(); repaint(); editorPane.requestFocus(); } } else { anchor = lastIndex = -1; repaint(); } } /** * Sets the end anchor and updates the state of the widget to reflect that * the click-and-drag gesture has ended. */ public void mouseReleased(MouseEvent e) { if (e.getSource() == this) { if (e.getX() < textWidth) { if (lastIndex != e.getY() / lineWidth + 1) { lastIndex = e.getY() / lineWidth + 1; doSelection(); repaint(); } editorPane.requestFocus(); } } else { anchor = lastIndex = -1; repaint(); } } /** * Temporarily moves the end anchor of the current selection. */ public void mouseDragged(MouseEvent e) { if (lastIndex != e.getY() / lineWidth + 1) { if (e.getX() < textWidth) { lastIndex = e.getY() / lineWidth + 1; doSelection(); repaint(); } } editorPane.requestFocus(); } /** * Empty - part of the MouseMotionListener interface. */ public void mouseMoved(MouseEvent e) {} /** * Empty - part of the MouseListener interface. */ public void mouseEntered(MouseEvent e) {} /** * Empty - part of the MouseListener interface. */ public void mouseExited(MouseEvent e) {} /** * Empty - part of the MouseListener interface. */ public void mouseClicked(MouseEvent e) {} /** * Listens for changes in caret position on the text pane. * <p/> * Updates the code block display and stuff */ public void caretUpdate(CaretEvent e) { repaint(); } }