org.fife.ui.rtextarea.IconRowHeader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.
/*
* 02/17/2009
*
* IconRowHeader.java - Renders icons in the gutter.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rtextarea;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JPanel;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Position;
import javax.swing.text.View;
/**
* Renders icons in the {@link Gutter}. This can be used to visually mark
* lines containing syntax errors, lines with breakpoints set on them, etc.
*
* This component has built-in support for displaying icons representing
* "bookmarks;" that is, lines a user can cycle through via F2 and Shift+F2.
* Bookmarked lines are toggled via Ctrl+F2, or by clicking in the icon area
* at the line to bookmark. In order to enable bookmarking, you must first
* assign an icon to represent a bookmarked line, then actually enable the
* feature. This is actually done on the parent {@link Gutter} component:
*
*
* Gutter gutter = scrollPane.getGutter();
* gutter.setBookmarkIcon(new ImageIcon("bookmark.png"));
* gutter.setBookmarkingEnabled(true);
*
*
* @author Robert Futrell
* @version 1.0
* @see org.fife.ui.rsyntaxtextarea.FoldingAwareIconRowHeader
*/
public class IconRowHeader extends AbstractGutterComponent implements MouseListener {
/**
* The icons to render.
*/
protected List trackingIcons;
/**
* The width of this component.
*/
protected int width;
/**
* Whether this component listens for mouse clicks and toggles "bookmark"
* icons on them.
*/
private boolean bookmarkingEnabled;
/**
* The icon to use for bookmarks.
*/
private Icon bookmarkIcon;
/**
* Used in {@link #paintComponent(Graphics)} to prevent reallocation on
* each paint.
*/
protected Rectangle visibleRect;
/**
* Used in {@link #paintComponent(Graphics)} to prevent reallocation on
* each paint.
*/
protected Insets textAreaInsets;
/**
* The first line in the active line range.
*/
protected int activeLineRangeStart;
/**
* The end line in the active line range.
*/
protected int activeLineRangeEnd;
/**
* The color used to highlight the active code block.
*/
private Color activeLineRangeColor;
/**
* Whether this component should use the gutter's background color (as
* opposed to using a LookAndFeel-dependent color, which is the default
* behavior).
*/
private boolean inheritsGutterBackground;
/**
* Constructor.
*
* @param textArea The parent text area.
*/
public IconRowHeader(RTextArea textArea) {
super(textArea);
}
/**
* Adds an icon that tracks an offset in the document, and is displayed
* adjacent to the line numbers. This is useful for marking things such
* as source code errors.
*
* @param offs The offset to track.
* @param icon The icon to display. This should be small (say 16x16).
* @return A tag for this icon.
* @throws BadLocationException If offs
is an invalid offset
* into the text area.
* @see #removeTrackingIcon(Object)
*/
public GutterIconInfo addOffsetTrackingIcon(int offs, Icon icon)
throws BadLocationException {
return addOffsetTrackingIcon(offs, icon, null);
}
/**
* Adds an icon that tracks an offset in the document, and is displayed
* adjacent to the line numbers. This is useful for marking things such
* as source code errors.
*
* @param offs The offset to track.
* @param icon The icon to display. This should be small (say 16x16).
* @param tip A tool tip for the icon.
* @return A tag for this icon.
* @throws BadLocationException If offs
is an invalid offset
* into the text area.
* @see #removeTrackingIcon(Object)
*/
public GutterIconInfo addOffsetTrackingIcon(int offs, Icon icon, String tip)
throws BadLocationException {
// Despite its documentation, AbstractDocument does *not* throw BLEs
// when creating sticky positions for offsets that do not exist.
// We must check for that ourselves.
if (offs < 0 || offs > textArea.getDocument().getLength()) {
throw new BadLocationException("Offset " + offs + " not in " +
"required range of 0-" + textArea.getDocument().getLength(),
offs);
}
Position pos = textArea.getDocument().createPosition(offs);
GutterIconImpl ti = new GutterIconImpl(icon, pos, tip);
if (trackingIcons==null) {
trackingIcons = new ArrayList(1); // Usually small
}
int index = Collections.binarySearch(trackingIcons, ti);
if (index<0) {
index = -(index+1);
}
trackingIcons.add(index, ti);
repaint();
return ti;
}
/**
* Clears the active line range.
*
* @see #setActiveLineRange(int, int)
*/
public void clearActiveLineRange() {
if (activeLineRangeStart!=-1 || activeLineRangeEnd!=-1) {
activeLineRangeStart = activeLineRangeEnd = -1;
repaint();
}
}
/**
* Returns the color used to paint the active line range, if any.
*
* @return The color.
* @see #setActiveLineRangeColor(Color)
*/
public Color getActiveLineRangeColor() {
return activeLineRangeColor;
}
/**
* Returns the icon to use for bookmarks.
*
* @return The icon to use for bookmarks. If this is null
,
* bookmarking is effectively disabled.
* @see #setBookmarkIcon(Icon)
* @see #isBookmarkingEnabled()
*/
public Icon getBookmarkIcon() {
return bookmarkIcon;
}
/**
* Returns the bookmarks known to this gutter.
*
* @return The bookmarks. If there are no bookmarks, an empty array is
* returned.
*/
public GutterIconInfo[] getBookmarks() {
List retVal = new ArrayList(1);
if (trackingIcons!=null) {
for (int i=0; i-1) {
GutterIconInfo[] infos = getTrackingIcons(line);
if (infos.length>0) {
// TODO: Display all messages?
return infos[infos.length-1].getToolTip();
}
}
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
return null;
}
protected GutterIconImpl getTrackingIcon(int index) {
return trackingIcons.get(index);
}
/**
* Returns the tracking icons at the specified line.
*
* @param line The line.
* @return The tracking icons at that line. If there are no tracking
* icons there, this will be an empty array.
* @throws BadLocationException If line
is invalid.
*/
public GutterIconInfo[] getTrackingIcons(int line)
throws BadLocationException {
List retVal = new ArrayList(1);
if (trackingIcons!=null) {
int start = textArea.getLineStartOffset(line);
int end = textArea.getLineEndOffset(line);
if (line==textArea.getLineCount()-1) {
end++; // Hack
}
for (int i=0; i=start && offs=end) {
break; // Quit early
}
}
}
GutterIconInfo[] array = new GutterIconInfo[retVal.size()];
return retVal.toArray(array);
}
@Override
protected void init() {
super.init();
visibleRect = new Rectangle();
width = 16;
addMouseListener(this);
activeLineRangeStart = activeLineRangeEnd = -1;
setActiveLineRangeColor(null);
// Must explicitly set our background color, otherwise we inherit that
// of the parent Gutter.
updateBackground();
ToolTipManager.sharedInstance().registerComponent(this);
}
/**
* Returns whether bookmarking is enabled.
*
* @return Whether bookmarking is enabled.
* @see #setBookmarkingEnabled(boolean)
*/
public boolean isBookmarkingEnabled() {
return bookmarkingEnabled;
}
/**
* {@inheritDoc}
*/
@Override
void lineHeightsChanged() {
repaint();
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
if (bookmarkingEnabled && bookmarkIcon!=null) {
try {
int line = viewToModelLine(e.getPoint());
if (line>-1) {
toggleBookmark(line);
}
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
}
/**
* {@inheritDoc}
*/
@Override
protected void paintComponent(Graphics g) {
if (textArea==null) {
return;
}
visibleRect = g.getClipBounds(visibleRect);
if (visibleRect==null) { // ???
visibleRect = getVisibleRect();
}
//System.out.println("IconRowHeader repainting: " + visibleRect);
if (visibleRect==null) {
return;
}
paintBackgroundImpl(g, visibleRect);
if (textArea.getLineWrap()) {
paintComponentWrapped(g);
return;
}
Document doc = textArea.getDocument();
Element root = doc.getDefaultRootElement();
textAreaInsets = textArea.getInsets(textAreaInsets);
if (visibleRect.y=topLine&&activeLineRangeStart<=bottomLine) ||
(activeLineRangeEnd>=topLine && activeLineRangeEnd<=bottomLine) ||
(activeLineRangeStart<=topLine && activeLineRangeEnd>=bottomLine)) {
g.setColor(activeLineRangeColor);
int firstLine = Math.max(activeLineRangeStart, topLine);
int y1 = firstLine * cellHeight + textAreaInsets.top;
int lastLine = Math.min(activeLineRangeEnd, bottomLine);
int y2 = (lastLine+1) * cellHeight + textAreaInsets.top - 1;
int j = y1;
while (j<=y2) {
int yEnd = Math.min(y2, j+getWidth());
int xEnd = yEnd-j;
g.drawLine(0,j, xEnd,yEnd);
j += 2;
}
int i = 2;
while (i=0; i--) { // Last to first
GutterIconInfo ti = getTrackingIcon(i);
int offs = ti.getMarkedOffset();
if (offs>=0 && offs<=doc.getLength()) {
int line = root.getElementIndex(offs);
if (line<=lastLine && line>=topLine) {
Icon icon = ti.getIcon();
if (icon!=null) {
int y2 = y + (line-topLine)*cellHeight;
y2 += (cellHeight-icon.getIconHeight())/2;
ti.getIcon().paintIcon(this, g, 0, y2);
lastLine = line-1; // Paint only 1 icon per line
}
}
else if (line at least 1 physical line, so it may be that
// y<0. The computed y-value is the y-value of the top of the first
// (possibly) partially-visible view.
Rectangle visibleEditorRect = ui.getVisibleEditorRect();
Rectangle r = IconRowHeader.getChildViewBounds(v, topLine,
visibleEditorRect);
int y = r.y;
int visibleBottom = visibleRect.y + visibleRect.height;
// Get the first possibly visible icon index.
int currentIcon = -1;
if (trackingIcons!=null) {
for (int i=0; i=0 && offs<=doc.getLength()) {
int line = root.getElementIndex(offs);
if (line>=topLine) {
currentIcon = i;
break;
}
}
}
}
// Keep painting lines until our y-coordinate is past the visible
// end of the text area.
g.setColor(getForeground());
int cellHeight = textArea.getLineHeight();
while (y < visibleBottom) {
r = getChildViewBounds(v, topLine, visibleEditorRect);
// int lineEndY = r.y+r.height;
/*
// Highlight the current line's line number, if desired.
if (currentLineHighlighted && topLine==currentLine) {
g.setColor(textArea.getCurrentLineHighlightColor());
g.fillRect(0,y, width,lineEndY-y);
g.setColor(getForeground());
}
*/
// Possibly paint an icon.
if (currentIcon>-1) {
// We want to paint the last icon added for this line.
GutterIconImpl toPaint = null;
while (currentIcon=0 && offs<=doc.getLength()) {
int line = root.getElementIndex(offs);
if (line==topLine) {
toPaint = ti;
}
else if (line>topLine) {
break;
}
}
currentIcon++;
}
if (toPaint!=null) {
Icon icon = toPaint.getIcon();
if (icon!=null) {
int y2 = y + (cellHeight-icon.getIconHeight())/2;
icon.paintIcon(this, g, 0, y2);
}
}
}
// The next possible y-coordinate is just after the last line
// painted.
y += r.height;
// Update topLine (we're actually using it for our "current line"
// variable now).
topLine++;
if (topLine>=lineCount) {
break;
}
}
}
/**
* Removes the specified tracking icon.
*
* @param tag A tag for a tracking icon.
* @see #removeAllTrackingIcons()
* @see #addOffsetTrackingIcon(int, Icon)
*/
public void removeTrackingIcon(Object tag) {
if (trackingIcons!=null && trackingIcons.remove(tag)) {
repaint();
}
}
/**
* Removes all tracking icons.
*
* @see #removeTrackingIcon(Object)
* @see #addOffsetTrackingIcon(int, Icon)
*/
public void removeAllTrackingIcons() {
if (trackingIcons!=null && trackingIcons.size()>0) {
trackingIcons.clear();
repaint();
}
}
/**
* Removes all bookmark tracking icons.
*/
private void removeBookmarkTrackingIcons() {
if (trackingIcons!=null) {
for (Iterator i=trackingIcons.iterator();
i.hasNext();) {
GutterIconImpl ti = i.next();
if (ti.getIcon()==bookmarkIcon) {
i.remove();
}
}
}
}
/**
* Highlights a range of lines in the icon area.
*
* @param startLine The start of the line range.
* @param endLine The end of the line range.
* @see #clearActiveLineRange()
*/
public void setActiveLineRange(int startLine, int endLine) {
if (startLine!=activeLineRangeStart ||
endLine!=activeLineRangeEnd) {
activeLineRangeStart = startLine;
activeLineRangeEnd = endLine;
repaint();
}
}
/**
* Sets the color to use to render active line ranges.
*
* @param color The color to use. If this is null, then the default
* color is used.
* @see #getActiveLineRangeColor()
* @see Gutter#DEFAULT_ACTIVE_LINE_RANGE_COLOR
*/
public void setActiveLineRangeColor(Color color) {
if (color==null) {
color = Gutter.DEFAULT_ACTIVE_LINE_RANGE_COLOR;
}
if (!color.equals(activeLineRangeColor)) {
activeLineRangeColor = color;
repaint();
}
}
/**
* Sets the icon to use for bookmarks. Any previous bookmark icons
* are removed.
*
* @param icon The new bookmark icon. If this is null
,
* bookmarking is effectively disabled.
* @see #getBookmarkIcon()
* @see #isBookmarkingEnabled()
*/
public void setBookmarkIcon(Icon icon) {
removeBookmarkTrackingIcons();
bookmarkIcon = icon;
repaint();
}
/**
* Sets whether bookmarking is enabled. Note that a bookmarking icon
* must be set via {@link #setBookmarkIcon(Icon)} before bookmarks are
* truly enabled.
*
* @param enabled Whether bookmarking is enabled. If this is
* false
, any bookmark icons are removed.
* @see #isBookmarkingEnabled()
* @see #setBookmarkIcon(Icon)
*/
public void setBookmarkingEnabled(boolean enabled) {
if (enabled!=bookmarkingEnabled) {
bookmarkingEnabled = enabled;
if (!enabled) {
removeBookmarkTrackingIcons();
}
repaint();
}
}
/**
* Sets whether the icon area inherits the gutter background (as opposed
* to painting with its own, default "panel" color, which is the default).
*
* @param inherits Whether the gutter background should be used in the icon
* row header. If this is false
, a default,
* Look-and-feel-dependent color is used.
*/
public void setInheritsGutterBackground(boolean inherits) {
if (inherits!=inheritsGutterBackground) {
inheritsGutterBackground = inherits;
repaint();
}
}
/**
* Sets the text area being displayed. This will clear any tracking
* icons currently displayed.
*
* @param textArea The text area.
*/
@Override
public void setTextArea(RTextArea textArea) {
removeAllTrackingIcons();
super.setTextArea(textArea);
}
/**
* Programatically toggles whether there is a bookmark for the specified
* line. If bookmarking is not enabled, this method does nothing.
*
* @param line The line.
* @return Whether a bookmark is now at the specified line.
* @throws BadLocationException If line
is an invalid line
* number in the text area.
*/
public boolean toggleBookmark(int line) throws BadLocationException {
if (!isBookmarkingEnabled() || getBookmarkIcon()==null) {
return false;
}
GutterIconInfo[] icons = getTrackingIcons(line);
if (icons.length==0) {
int offs = textArea.getLineStartOffset(line);
addOffsetTrackingIcon(offs, bookmarkIcon);
return true;
}
boolean found = false;
for (int i=0; i 1
// bookmark is on a single line (kind of flaky, but it
// works...). If they delete all chars in the document,
// AbstractDocument gets a little flaky with the returned line
// number for viewToModel(), so this is just us trying to save
// face a little.
}
}
if (!found) {
int offs = textArea.getLineStartOffset(line);
addOffsetTrackingIcon(offs, bookmarkIcon);
}
return !found;
}
/**
* Sets our background color to that of standard "panels" in this
* LookAndFeel. This is necessary because, otherwise, we'd inherit the
* background color of our parent component (the Gutter).
*/
private void updateBackground() {
Color bg = UIManager.getColor("Panel.background");
if (bg==null) { // UIManager properties aren't guaranteed to exist
bg = new JPanel().getBackground();
}
setBackground(bg);
}
/**
* {@inheritDoc}
*/
@Override
public void updateUI() {
super.updateUI(); // Does nothing
updateBackground();
}
/**
* Returns the line rendered at the specified location.
*
* @param p The location in this row header.
* @return The corresponding line in the editor.
* @throws BadLocationException ble If an error occurs.
*/
private int viewToModelLine(Point p) throws BadLocationException {
int offs = textArea.viewToModel(p);
return offs>-1 ? textArea.getLineOfOffset(offs) : -1;
}
/**
* Implementation of the icons rendered.
*/
private static class GutterIconImpl implements GutterIconInfo,
Comparable {
private Icon icon;
private Position pos;
private String toolTip;
GutterIconImpl(Icon icon, Position pos, String toolTip) {
this.icon = icon;
this.pos = pos;
this.toolTip = toolTip;
}
@Override
public int compareTo(GutterIconInfo other) {
if (other!=null) {
return pos.getOffset() - other.getMarkedOffset();
}
return -1;
}
@Override
public boolean equals(Object o) {
return o==this;
}
@Override
public Icon getIcon() {
return icon;
}
@Override
public int getMarkedOffset() {
return pos.getOffset();
}
@Override
public String getToolTip() {
return toolTip;
}
@Override
public int hashCode() {
return icon.hashCode(); // FindBugs
}
}
}