
org.fife.ui.rtextarea.FoldIndicator 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.
/*
* 10/08/2011
*
* FoldIndicator.java - Gutter component allowing the user to expand and
* collapse folds.
*
* 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.Component;
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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Icon;
import javax.swing.JToolTip;
import javax.swing.ToolTipManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.View;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.focusabletip.TipUtil;
import org.fife.ui.rsyntaxtextarea.folding.Fold;
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
/**
* Component in the gutter that displays +/- icons to expand and collapse
* fold regions in the editor.
*
* @author Robert Futrell
* @version 1.0
*/
public class FoldIndicator extends AbstractGutterComponent {
/**
* Used in {@link #paintComponent(Graphics)} to prevent reallocation on
* each paint.
*/
private Insets textAreaInsets;
/**
* Used in {@link #paintComponent(Graphics)} to prevent reallocation on
* each paint.
*/
private Rectangle visibleRect;
/**
* The fold to show the outline line for.
*/
private Fold foldWithOutlineShowing;
/**
* The color to use for fold icon backgrounds, if the default icons
* are used.
*/
private Color foldIconBackground;
/**
* The icon used for collapsed folds.
*/
private Icon collapsedFoldIcon;
/**
* The icon used for expanded folds.
*/
private Icon expandedFoldIcon;
/**
* Whether tool tips are displayed showing the contents of collapsed
* fold regions.
*/
private boolean showFoldRegionTips;
/**
* The color used to paint fold outlines.
*/
static final Color DEFAULT_FOREGROUND = Color.gray;
/**
* The default color used to paint the "inside" of fold icons.
*/
static final Color DEFAULT_FOLD_BACKGROUND = Color.white;
/**
* Listens for events in this component.
*/
private Listener listener;
/**
* Width of this component.
*/
private static final int WIDTH = 12;
public FoldIndicator(RTextArea textArea) {
super(textArea);
}
/**
* Overridden to use the editor's background if it's detected that the
* user isn't using white as the editor bg, but the system's tool tip
* background is yellow-ish.
*
* @return The tool tip.
*/
@Override
public JToolTip createToolTip() {
JToolTip tip = super.createToolTip();
Color textAreaBG = textArea.getBackground();
if (textAreaBG!=null && !Color.white.equals(textAreaBG)) {
Color bg = TipUtil.getToolTipBackground();
// If current L&F's tool tip color is close enough to "yellow",
// and we're not using the default text background of white, use
// the editor background as the tool tip background.
if (bg.getRed()>=240 && bg.getGreen()>=240 && bg.getBlue()>=200) {
tip.setBackground(textAreaBG);
}
}
return tip;
}
private Fold findOpenFoldClosestTo(Point p) {
Fold fold = null;
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
if (rsta.isCodeFoldingEnabled()) { // Should always be true
int offs = rsta.viewToModel(p); // TODO: Optimize me
if (offs>-1) {
try {
int line = rsta.getLineOfOffset(offs);
FoldManager fm = rsta.getFoldManager();
fold = fm.getFoldForLine(line);
if (fold==null) {
fold = fm.getDeepestOpenFoldContaining(offs);
}
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
}
}
return fold;
}
/**
* Returns the color to use for the "background" of fold icons. This
* is be ignored if custom icons are used.
*
* @return The background color.
* @see #setFoldIconBackground(Color)
*/
public Color getFoldIconBackground() {
return foldIconBackground;
}
@Override
public Dimension getPreferredSize() {
int h = textArea!=null ? textArea.getHeight() : 100; // Arbitrary
return new Dimension(WIDTH, h);
}
/**
* Returns whether tool tips are displayed showing the contents of
* collapsed fold regions when the mouse hovers over a +/- icon.
*
* @return Whether these tool tips are displayed.
* @see #setShowCollapsedRegionToolTips(boolean)
*/
public boolean getShowCollapsedRegionToolTips() {
return showFoldRegionTips;
}
/**
* Positions tool tips to be aligned in the text component, so that the
* displayed content is shown (almost) exactly where it would be in the
* editor.
*
* @param e The mouse location.
*/
@Override
public Point getToolTipLocation(MouseEvent e) {
// ToolTipManager requires both location and text to be null to hide
// a currently-visible tool tip window. If text is null but location
// has some value, it will show a tool tip with empty content, the size
// of its border (!).
String text = getToolTipText(e);
if (text==null) {
return null;
}
// Try to overlap the tip's text directly over the code
Point p = e.getPoint();
p.y = (p.y/textArea.getLineHeight()) * textArea.getLineHeight();
p.x = getWidth() + textArea.getMargin().left;
Gutter gutter = getGutter();
int gutterMargin = gutter.getInsets().right;
p.x += gutterMargin;
JToolTip tempTip = createToolTip();
p.x -= tempTip.getInsets().left;
p.y += 16;
return p;
}
/**
* Overridden to show the content of a collapsed fold on mouse-overs.
*
* @param e The mouse location.
*/
@Override
public String getToolTipText(MouseEvent e) {
String text = null;
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
if (rsta.isCodeFoldingEnabled()) {
FoldManager fm = rsta.getFoldManager();
int pos = rsta.viewToModel(new Point(0, e.getY()));
if (pos>=0) { // Not -1
int line = 0;
try {
line = rsta.getLineOfOffset(pos);
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
return null;
}
Fold fold = fm.getFoldForLine(line);
if (fold!=null && fold.isCollapsed()) {
int endLine = fold.getEndLine();
if (fold.getLineCount()>25) { // Not too big
endLine = fold.getStartLine() + 25;
}
StringBuilder sb = new StringBuilder("");
while (line<=endLine && line");
line++;
}
text = sb.toString();
}
}
}
return text;
}
@Override
void handleDocumentEvent(DocumentEvent e) {
int newLineCount = textArea.getLineCount();
if (newLineCount!=currentLineCount) {
currentLineCount = newLineCount;
repaint();
}
}
@Override
protected void init() {
super.init();
setForeground(DEFAULT_FOREGROUND);
setFoldIconBackground(DEFAULT_FOLD_BACKGROUND);
collapsedFoldIcon = new FoldIcon(true);
expandedFoldIcon = new FoldIcon(false);
listener = new Listener(this);
visibleRect = new Rectangle();
setShowCollapsedRegionToolTips(true);
}
@Override
void lineHeightsChanged() {
// TODO Auto-generated method stub
}
@Override
protected void paintComponent(Graphics g) {
if (textArea==null) {
return;
}
visibleRect = g.getClipBounds(visibleRect);
if (visibleRect==null) { // ???
visibleRect = getVisibleRect();
}
//System.out.println("FoldIndicator repainting: " + visibleRect);
if (visibleRect==null) {
return;
}
Color bg = getBackground();
if (getGutter()!=null) { // Should always be true
bg = getGutter().getBackground();
}
g.setColor(bg);
g.fillRect(0,visibleRect.y, getWidth(),visibleRect.height);
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
if (!rsta.isCodeFoldingEnabled()) {
return; // We should be hidden in this case, but still...
}
if (textArea.getLineWrap()) {
paintComponentWrapped(g);
return;
}
// Get where to start painting (top of the row).
// We need to be "scrolled up" up just enough for the missing part of
// the first line.
textAreaInsets = textArea.getInsets(textAreaInsets);
if (visibleRect.y 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 = LineNumberList.getChildViewBounds(v, topLine,
visibleEditorRect);
int y = r.y;
y += (cellHeight-collapsedFoldIcon.getIconHeight())/2;
int visibleBottom = visibleRect.y + visibleRect.height;
int x = width - 10;
int line = topLine;
boolean paintingOutlineLine = foldWithOutlineShowing!=null &&
foldWithOutlineShowing.containsLine(line);
int lineCount = root.getElementCount();
while (y-1) {
line = textArea.getLineOfOffset(offs);
}
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
return line;
}
/**
* Sets the color to use for the "background" of fold icons. This will
* be ignored if custom icons are used.
*
* @param bg The new background color.
* @see #getFoldIconBackground()
*/
public void setFoldIconBackground(Color bg) {
foldIconBackground = bg;
}
/**
* Sets the icons to use to represent collapsed and expanded folds.
*
* @param collapsedIcon The collapsed fold icon. This cannot be
* null
.
* @param expandedIcon The expanded fold icon. This cannot be
* null
.
*/
public void setFoldIcons(Icon collapsedIcon, Icon expandedIcon) {
this.collapsedFoldIcon = collapsedIcon;
this.expandedFoldIcon = expandedIcon;
revalidate(); // Icons may be different sizes.
repaint();
}
/**
* Toggles whether tool tips should be displayed showing the contents of
* collapsed fold regions when the mouse hovers over a +/- icon.
*
* @param show Whether to show these tool tips.
* @see #getShowCollapsedRegionToolTips()
*/
public void setShowCollapsedRegionToolTips(boolean show) {
if (show!=showFoldRegionTips) {
if (show) {
ToolTipManager.sharedInstance().registerComponent(this);
}
else {
ToolTipManager.sharedInstance().unregisterComponent(this);
}
showFoldRegionTips = show;
}
}
/**
* Overridden so we can track when code folding is enabled/disabled.
*/
@Override
public void setTextArea(RTextArea textArea) {
if (this.textArea!=null) {
this.textArea.removePropertyChangeListener(
RSyntaxTextArea.CODE_FOLDING_PROPERTY, listener);
}
super.setTextArea(textArea);
if (this.textArea!=null) {
this.textArea.addPropertyChangeListener(
RSyntaxTextArea.CODE_FOLDING_PROPERTY, listener);
}
}
/**
* The default +/- icon for expanding and collapsing folds.
*/
private class FoldIcon implements Icon {
private boolean collapsed;
public FoldIcon(boolean collapsed) {
this.collapsed = collapsed;
}
public int getIconHeight() {
return 8;
}
public int getIconWidth() {
return 8;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(foldIconBackground);
g.fillRect(x,y, 8,8);
g.setColor(getForeground());
g.drawRect(x,y, 8,8);
g.drawLine(x+2,y+4, x+2+4,y+4);
if (collapsed) {
g.drawLine(x+4,y+2, x+4,y+6);
}
}
}
/**
* Listens for events in this component.
*/
private class Listener extends MouseInputAdapter
implements PropertyChangeListener {
public Listener(FoldIndicator fgc) {
fgc.addMouseListener(this);
fgc.addMouseMotionListener(this);
}
@Override
public void mouseClicked(MouseEvent e) {
// // TODO: Implement code folding with word wrap enabled
// if (textArea.getLineWrap()) {
// UIManager.getLookAndFeel().provideErrorFeedback(textArea);
// return;
// }
Point p = e.getPoint();
int line = rowAtPoint(p);
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
FoldManager fm = rsta.getFoldManager();
Fold fold = fm.getFoldForLine(line);
if (fold!=null) {
fold.toggleCollapsedState();
getGutter().repaint();
textArea.repaint();
}
}
@Override
public void mouseExited(MouseEvent e) {
if (foldWithOutlineShowing!=null) {
foldWithOutlineShowing = null;
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
Fold newSelectedFold = findOpenFoldClosestTo(e.getPoint());
if (newSelectedFold!=foldWithOutlineShowing &&
newSelectedFold!=null && !newSelectedFold.isOnSingleLine()) {
foldWithOutlineShowing = newSelectedFold;
repaint();
}
}
public void propertyChange(PropertyChangeEvent e) {
// Whether folding is enabled in the editor has changed.
repaint();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy