All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rtextarea.IconRowHeader Maven / Gradle / Ivy

/*
 * 02/17/2009
 *
 * IconRowHeader.java - Renders icons in the gutter.
 *
 * This library is distributed under a modified BSD license.  See the included
 * LICENSE 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.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(GutterIconInfo) */ 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(GutterIconInfo) */ 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(GutterIconInfo tag) { if (trackingIcons!=null && trackingIcons.remove(tag)) { repaint(); } } /** * Removes all tracking icons. * * @see #removeTrackingIcon(GutterIconInfo) * @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) { trackingIcons.removeIf(ti -> ti.getIcon() == bookmarkIcon); } } /** * 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 (GutterIconInfo icon : icons) { if (icon.getIcon() == bookmarkIcon) { removeTrackingIcon(icon); found = true; // Don't quit, in case they manipulate the document so > 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 } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy