|
What this is
Other links
The source code
/*
* Gutter.java
* :tabSize=8:indentSize=8:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1999, 2000 mike dillon
* Portions copyright (C) 2001, 2002 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.textarea;
//{{{ Imports
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import org.gjt.sp.jedit.*;
import org.gjt.sp.util.Log;
//}}}
/**
* The gutter is the component that displays folding triangles and line
* numbers to the left of the text area. The only methods in this class
* that should be called by plugins are those for adding and removing
* text area extensions.
*
* @see #addExtension(TextAreaExtension)
* @see #addExtension(int,TextAreaExtension)
* @see #removeExtension(TextAreaExtension)
* @see TextAreaExtension
* @see JEditTextArea
*
* @author Mike Dillon and Slava Pestov
* @version $Id: Gutter.java,v 1.51 2004/08/12 22:42:44 spestov Exp $
*/
public class Gutter extends JComponent implements SwingConstants
{
//{{{ Layers
/**
* The lowest possible layer.
* @see #addExtension(int,TextAreaExtension)
* @since jEdit 4.0pre4
*/
public static final int LOWEST_LAYER = Integer.MIN_VALUE;
/**
* Default extension layer. This is above the wrap guide but below the
* bracket highlight.
* @since jEdit 4.0pre4
*/
public static final int DEFAULT_LAYER = 0;
/**
* Highest possible layer.
* @since jEdit 4.0pre4
*/
public static final int HIGHEST_LAYER = Integer.MAX_VALUE;
//}}}
//{{{ Gutter constructor
public Gutter(View view, JEditTextArea textArea)
{
this.view = view;
this.textArea = textArea;
setAutoscrolls(true);
setOpaque(true);
setRequestFocusEnabled(false);
extensionMgr = new ExtensionManager();
MouseHandler ml = new MouseHandler();
addMouseListener(ml);
addMouseMotionListener(ml);
addExtension(new MarkerHighlight());
updateBorder();
} //}}}
//{{{ paintComponent() method
public void paintComponent(Graphics _gfx)
{
Graphics2D gfx = (Graphics2D)_gfx;
// fill the background
Rectangle clip = gfx.getClipBounds();
gfx.setColor(getBackground());
gfx.fillRect(clip.x, clip.y, clip.width, clip.height);
// if buffer is loading, don't paint anything
if (!textArea.getBuffer().isLoaded())
return;
int lineHeight = textArea.getPainter().getFontMetrics()
.getHeight();
if(lineHeight == 0)
return;
int firstLine = clip.y / lineHeight;
int lastLine = (clip.y + clip.height - 1) / lineHeight;
if(lastLine - firstLine > textArea.getVisibleLines())
{
Log.log(Log.ERROR,this,"BUG: firstLine=" + firstLine);
Log.log(Log.ERROR,this," lastLine=" + lastLine);
Log.log(Log.ERROR,this," visibleLines=" + textArea.getVisibleLines());
Log.log(Log.ERROR,this," height=" + getHeight());
Log.log(Log.ERROR,this," painter.height=" + textArea.getPainter().getHeight());
Log.log(Log.ERROR,this," clip.y=" + clip.y);
Log.log(Log.ERROR,this," clip.height=" + clip.height);
Log.log(Log.ERROR,this," lineHeight=" + lineHeight);
}
int y = (clip.y - clip.y % lineHeight);
extensionMgr.paintScreenLineRange(textArea,gfx,
firstLine,lastLine,y,lineHeight);
for (int line = firstLine; line <= lastLine;
line++, y += lineHeight)
{
paintLine(gfx,line,y);
}
} //}}}
//{{{ addExtension() method
/**
* Adds a text area extension, which can perform custom painting and
* tool tip handling.
* @param extension The extension
* @since jEdit 4.0pre4
*/
public void addExtension(TextAreaExtension extension)
{
extensionMgr.addExtension(DEFAULT_LAYER,extension);
repaint();
} //}}}
//{{{ addExtension() method
/**
* Adds a text area extension, which can perform custom painting and
* tool tip handling.
* @param layer The layer to add the extension to. Note that more than
* extension can share the same layer.
* @param extension The extension
* @since jEdit 4.0pre4
*/
public void addExtension(int layer, TextAreaExtension extension)
{
extensionMgr.addExtension(layer,extension);
repaint();
} //}}}
//{{{ removeExtension() method
/**
* Removes a text area extension. It will no longer be asked to
* perform custom painting and tool tip handling.
* @param extension The extension
* @since jEdit 4.0pre4
*/
public void removeExtension(TextAreaExtension extension)
{
extensionMgr.removeExtension(extension);
repaint();
} //}}}
//{{{ getExtensions() method
/**
* Returns an array of registered text area extensions. Useful for
* debugging purposes.
* @since jEdit 4.1pre5
*/
public TextAreaExtension[] getExtensions()
{
return extensionMgr.getExtensions();
} //}}}
//{{{ getToolTipText() method
/**
* Returns the tool tip to display at the specified location.
* @param evt The mouse event
*/
public String getToolTipText(MouseEvent evt)
{
if(!textArea.getBuffer().isLoaded())
return null;
return extensionMgr.getToolTipText(evt.getX(),evt.getY());
} //}}}
//{{{ setBorder() method
/**
* Convenience method for setting a default matte border on the right
* with the specified border width and color
* @param width The border width (in pixels)
* @param color1 The focused border color
* @param color2 The unfocused border color
* @param color3 The gutter/text area gap color
*/
public void setBorder(int width, Color color1, Color color2, Color color3)
{
this.borderWidth = width;
focusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
new MatteBorder(0,0,0,width,color1));
noFocusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
new MatteBorder(0,0,0,width,color2));
updateBorder();
} //}}}
//{{{ updateBorder() method
/**
* Sets the border differently if the text area has focus or not.
*/
public void updateBorder()
{
if(view.getEditPane() == null)
setBorder(noFocusBorder);
else if(view.getEditPane().getTextArea() == textArea)
setBorder(focusBorder);
else
setBorder(noFocusBorder);
} //}}}
//{{{ setBorder() method
/*
* JComponent.setBorder(Border) is overridden here to cache the left
* inset of the border (if any) to avoid having to fetch it during every
* repaint.
*/
public void setBorder(Border border)
{
super.setBorder(border);
if (border == null)
{
collapsedSize.width = 0;
collapsedSize.height = 0;
}
else
{
Insets insets = border.getBorderInsets(this);
collapsedSize.width = FOLD_MARKER_SIZE + insets.right;
collapsedSize.height = gutterSize.height
= insets.top + insets.bottom;
gutterSize.width = FOLD_MARKER_SIZE + insets.right
+ fm.stringWidth("12345");
}
revalidate();
} //}}}
//{{{ setFont() method
/*
* JComponent.setFont(Font) is overridden here to cache the baseline for
* the font. This avoids having to get the font metrics during every
* repaint.
*/
public void setFont(Font font)
{
super.setFont(font);
fm = getFontMetrics(font);
baseline = fm.getAscent();
Border border = getBorder();
if(border != null)
{
gutterSize.width = FOLD_MARKER_SIZE
+ border.getBorderInsets(this).right
+ fm.stringWidth("12345");
revalidate();
}
} //}}}
//{{{ Getters and setters
//{{{ getHighlightedForeground() method
/**
* Get the foreground color for highlighted line numbers
* @return The highlight color
*/
public Color getHighlightedForeground()
{
return intervalHighlight;
} //}}}
//{{{ setHighlightedForeground() method
public void setHighlightedForeground(Color highlight)
{
intervalHighlight = highlight;
} //}}}
//{{{ getCurrentLineForeground() method
public Color getCurrentLineForeground()
{
return currentLineHighlight;
} //}}}
//{{{ setCurrentLineForeground() method
public void setCurrentLineForeground(Color highlight)
{
currentLineHighlight = highlight;
} //}}}
//{{{ getFoldColor() method
public Color getFoldColor()
{
return foldColor;
} //}}}
//{{{ setFoldColor() method
public void setFoldColor(Color foldColor)
{
this.foldColor = foldColor;
} //}}}
//{{{ getPreferredSize() method
/*
* Component.getPreferredSize() is overridden here to support the
* collapsing behavior.
*/
public Dimension getPreferredSize()
{
if (expanded)
return gutterSize;
else
return collapsedSize;
} //}}}
//{{{ getMinimumSize() method
public Dimension getMinimumSize()
{
return getPreferredSize();
} //}}}
//{{{ getLineNumberAlignment() method
/**
* Identifies whether the horizontal alignment of the line numbers.
* @return Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
*/
public int getLineNumberAlignment()
{
return alignment;
} //}}}
//{{{ setLineNumberAlignment() method
/**
* Sets the horizontal alignment of the line numbers.
* @param alignment Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
*/
public void setLineNumberAlignment(int alignment)
{
if (this.alignment == alignment) return;
this.alignment = alignment;
repaint();
} //}}}
//{{{ isExpanded() method
/**
* Identifies whether the gutter is collapsed or expanded.
* @return true if the gutter is expanded, false if it is collapsed
*/
public boolean isExpanded()
{
return expanded;
} //}}}
//{{{ setExpanded() method
/**
* Sets whether the gutter is collapsed or expanded and force the text
* area to update its layout if there is a change.
* @param expanded true if the gutter is expanded,
* false if it is collapsed
*/
public void setExpanded(boolean expanded)
{
if (this.expanded == expanded) return;
this.expanded = expanded;
textArea.revalidate();
} //}}}
//{{{ toggleExpanded() method
/**
* Toggles whether the gutter is collapsed or expanded.
*/
public void toggleExpanded()
{
setExpanded(!expanded);
} //}}}
//{{{ getHighlightInterval() method
/**
* Sets the number of lines between highlighted line numbers.
* @return The number of lines between highlighted line numbers or
* zero if highlighting is disabled
*/
public int getHighlightInterval()
{
return interval;
} //}}}
//{{{ setHighlightInterval() method
/**
* Sets the number of lines between highlighted line numbers. Any value
* less than or equal to one will result in highlighting being disabled.
* @param interval The number of lines between highlighted line numbers
*/
public void setHighlightInterval(int interval)
{
if (interval <= 1) interval = 0;
this.interval = interval;
repaint();
} //}}}
//{{{ isCurrentLineHighlightEnabled() method
public boolean isCurrentLineHighlightEnabled()
{
return currentLineHighlightEnabled;
} //}}}
//{{{ setCurrentLineHighlightEnabled() method
public void setCurrentLineHighlightEnabled(boolean enabled)
{
if (currentLineHighlightEnabled == enabled) return;
currentLineHighlightEnabled = enabled;
repaint();
} //}}}
//{{{ getStructureHighlightColor() method
/**
* Returns the structure highlight color.
* @since jEdit 4.2pre3
*/
public final Color getStructureHighlightColor()
{
return structureHighlightColor;
} //}}}
//{{{ setStructureHighlightColor() method
/**
* Sets the structure highlight color.
* @param structureHighlightColor The structure highlight color
* @since jEdit 4.2pre3
*/
public final void setStructureHighlightColor(Color structureHighlightColor)
{
this.structureHighlightColor = structureHighlightColor;
repaint();
} //}}}
//{{{ isStructureHighlightEnabled() method
/**
* Returns true if structure highlighting is enabled, false otherwise.
* @since jEdit 4.2pre3
*/
public final boolean isStructureHighlightEnabled()
{
return structureHighlight;
} //}}}
//{{{ setStructureHighlightEnabled() method
/**
* Enables or disables structure highlighting.
* @param structureHighlight True if structure highlighting should be
* enabled, false otherwise
* @since jEdit 4.2pre3
*/
public final void setStructureHighlightEnabled(boolean structureHighlight)
{
this.structureHighlight = structureHighlight;
repaint();
} //}}}
//{{{ getMarkerHighlightColor() method
public Color getMarkerHighlightColor()
{
return markerHighlightColor;
} //}}}
//{{{ setMarkerHighlightColor() method
public void setMarkerHighlightColor(Color markerHighlightColor)
{
this.markerHighlightColor = markerHighlightColor;
} //}}}
//{{{ isMarkerHighlightEnabled() method
public boolean isMarkerHighlightEnabled()
{
return markerHighlight;
} //}}}
//{{{ isMarkerHighlightEnabled()
public void setMarkerHighlightEnabled(boolean markerHighlight)
{
this.markerHighlight = markerHighlight;
} //}}}
//}}}
//{{{ Private members
//{{{ Instance variables
private static final int FOLD_MARKER_SIZE = 12;
private View view;
private JEditTextArea textArea;
private ExtensionManager extensionMgr;
private int baseline;
private Dimension gutterSize = new Dimension(0,0);
private Dimension collapsedSize = new Dimension(0,0);
private Color intervalHighlight;
private Color currentLineHighlight;
private Color foldColor;
private FontMetrics fm;
private int alignment;
private int interval;
private boolean currentLineHighlightEnabled;
private boolean expanded;
private boolean structureHighlight;
private Color structureHighlightColor;
private boolean markerHighlight;
private Color markerHighlightColor;
private int borderWidth;
private Border focusBorder, noFocusBorder;
//}}}
//{{{ paintLine() method
private void paintLine(Graphics2D gfx, int line, int y)
{
Buffer buffer = textArea.getBuffer();
if(!buffer.isLoaded())
return;
int lineHeight = textArea.getPainter().getFontMetrics()
.getHeight();
ChunkCache.LineInfo info = textArea.chunkCache.getLineInfo(line);
int physicalLine = info.physicalLine;
// Skip lines beyond EOF
if(physicalLine == -1)
return;
//{{{ Paint fold triangles
if(info.firstSubregion && buffer.isFoldStart(physicalLine))
{
int _y = y + lineHeight / 2;
gfx.setColor(foldColor);
if(textArea.displayManager
.isLineVisible(physicalLine + 1))
{
gfx.drawLine(1,_y - 3,10,_y - 3);
gfx.drawLine(2,_y - 2,9,_y - 2);
gfx.drawLine(3,_y - 1,8,_y - 1);
gfx.drawLine(4,_y,7,_y);
gfx.drawLine(5,_y + 1,6,_y + 1);
}
else
{
gfx.drawLine(4,_y - 5,4,_y + 4);
gfx.drawLine(5,_y - 4,5,_y + 3);
gfx.drawLine(6,_y - 3,6,_y + 2);
gfx.drawLine(7,_y - 2,7,_y + 1);
gfx.drawLine(8,_y - 1,8,_y);
}
}
else if(info.lastSubregion && buffer.isFoldEnd(physicalLine))
{
gfx.setColor(foldColor);
int _y = y + lineHeight / 2;
gfx.drawLine(4,_y,4,_y + 3);
gfx.drawLine(4,_y + 3,7,_y + 3);
} //}}}
//{{{ Paint bracket scope
else if(structureHighlight)
{
StructureMatcher.Match match = textArea.getStructureMatch();
int caretLine = textArea.getCaretLine();
if(textArea.isStructureHighlightVisible()
&& physicalLine >= Math.min(caretLine,match.startLine)
&& physicalLine <= Math.max(caretLine,match.startLine))
{
int caretScreenLine;
if(caretLine > textArea.getLastPhysicalLine())
caretScreenLine = Integer.MAX_VALUE;
else if(textArea.displayManager.isLineVisible(
textArea.getCaretLine()))
{
caretScreenLine = textArea
.getScreenLineOfOffset(
textArea.getCaretPosition());
}
else
{
caretScreenLine = -1;
}
int structScreenLine;
if(match.startLine > textArea.getLastPhysicalLine())
structScreenLine = Integer.MAX_VALUE;
else if(textArea.displayManager.isLineVisible(
match.startLine))
{
structScreenLine = textArea
.getScreenLineOfOffset(
match.start);
}
else
{
structScreenLine = -1;
}
if(caretScreenLine > structScreenLine)
{
int tmp = caretScreenLine;
caretScreenLine = structScreenLine;
structScreenLine = tmp;
}
gfx.setColor(structureHighlightColor);
if(structScreenLine == caretScreenLine)
{
// do nothing
}
// draw |^
else if(line == caretScreenLine)
{
gfx.fillRect(5,
y
+ lineHeight / 2,
5,
2);
gfx.fillRect(5,
y
+ lineHeight / 2,
2,
lineHeight - lineHeight / 2);
}
// draw |_
else if(line == structScreenLine)
{
gfx.fillRect(5,
y,
2,
lineHeight / 2);
gfx.fillRect(5,
y + lineHeight / 2,
5,
2);
}
// draw |
else if(line > caretScreenLine
&& line < structScreenLine)
{
gfx.fillRect(5,
y,
2,
lineHeight);
}
}
} //}}}
//{{{ Paint line numbers
if(info.firstSubregion && expanded)
{
String number = Integer.toString(physicalLine + 1);
int offset;
switch (alignment)
{
case RIGHT:
offset = gutterSize.width - collapsedSize.width
- (fm.stringWidth(number) + 1);
break;
case CENTER:
offset = ((gutterSize.width - collapsedSize.width)
- fm.stringWidth(number)) / 2;
break;
case LEFT: default:
offset = 0;
break;
}
boolean highlightCurrentLine = currentLineHighlightEnabled
&& textArea.selection.size() == 0;
if (physicalLine == textArea.getCaretLine() && highlightCurrentLine)
{
gfx.setColor(currentLineHighlight);
}
else if (interval > 1 && (line
+ textArea.getFirstLine() + 1)
% interval == 0)
gfx.setColor(intervalHighlight);
else
gfx.setColor(getForeground());
gfx.drawString(number, FOLD_MARKER_SIZE + offset,
baseline + y);
} //}}}
} //}}}
//}}}
//{{{ MouseHandler class
class MouseHandler extends MouseInputAdapter
{
MouseActions mouseActions = new MouseActions("gutter");
boolean drag;
int toolTipInitialDelay, toolTipReshowDelay;
//{{{ mouseEntered() method
public void mouseEntered(MouseEvent e)
{
ToolTipManager ttm = ToolTipManager.sharedInstance();
toolTipInitialDelay = ttm.getInitialDelay();
toolTipReshowDelay = ttm.getReshowDelay();
ttm.setInitialDelay(0);
ttm.setReshowDelay(0);
} //}}}
//{{{ mouseExited() method
public void mouseExited(MouseEvent evt)
{
ToolTipManager ttm = ToolTipManager.sharedInstance();
ttm.setInitialDelay(toolTipInitialDelay);
ttm.setReshowDelay(toolTipReshowDelay);
} //}}}
//{{{ mousePressed() method
public void mousePressed(MouseEvent e)
{
textArea.requestFocus();
if(GUIUtilities.isPopupTrigger(e)
|| e.getX() >= getWidth() - borderWidth * 2)
{
e.translatePoint(-getWidth(),0);
textArea.mouseHandler.mousePressed(e);
drag = true;
}
else
{
Buffer buffer = textArea.getBuffer();
int screenLine = e.getY() / textArea.getPainter()
.getFontMetrics().getHeight();
int line = textArea.chunkCache.getLineInfo(screenLine)
.physicalLine;
if(line == -1)
return;
//{{{ Determine action
String defaultAction;
String variant;
if(buffer.isFoldStart(line))
{
defaultAction = "toggle-fold";
variant = "fold";
}
else if(structureHighlight
&& textArea.isStructureHighlightVisible()
&& textArea.lineInStructureScope(line))
{
defaultAction = "match-struct";
variant = "struct";
}
else
return;
String action = mouseActions.getActionForEvent(
e,variant);
if(action == null)
action = defaultAction;
//}}}
//{{{ Handle actions
StructureMatcher.Match match = textArea
.getStructureMatch();
if(action.equals("select-fold"))
{
textArea.displayManager.expandFold(line,true);
textArea.selectFold(line);
}
else if(action.equals("narrow-fold"))
{
int[] lines = buffer.getFoldAtLine(line);
textArea.displayManager.narrow(lines[0],lines[1]);
}
else if(action.startsWith("toggle-fold"))
{
if(textArea.displayManager
.isLineVisible(line + 1))
{
textArea.displayManager.collapseFold(line);
}
else
{
if(action.endsWith("-fully"))
{
textArea.displayManager
.expandFold(line,
true);
}
else
{
textArea.displayManager
.expandFold(line,
false);
}
}
}
else if(action.equals("match-struct"))
{
if(match != null)
textArea.setCaretPosition(match.end);
}
else if(action.equals("select-struct"))
{
if(match != null)
{
match.matcher.selectMatch(
textArea);
}
}
else if(action.equals("narrow-struct"))
{
if(match != null)
{
int start = Math.min(
match.startLine,
textArea.getCaretLine());
int end = Math.max(
match.endLine,
textArea.getCaretLine());
textArea.displayManager.narrow(start,end);
}
} //}}}
}
} //}}}
//{{{ mouseDragged() method
public void mouseDragged(MouseEvent e)
{
if(drag /* && e.getX() >= getWidth() - borderWidth * 2 */)
{
e.translatePoint(-getWidth(),0);
textArea.mouseHandler.mouseDragged(e);
}
} //}}}
//{{{ mouseReleased() method
public void mouseReleased(MouseEvent e)
{
if(drag && e.getX() >= getWidth() - borderWidth * 2)
{
e.translatePoint(-getWidth(),0);
textArea.mouseHandler.mouseReleased(e);
}
drag = false;
} //}}}
} //}}}
//{{{ MarkerHighlight class
class MarkerHighlight extends TextAreaExtension
{
//{{{ paintValidLine() method
public void paintValidLine(Graphics2D gfx, int screenLine,
int physicalLine, int start, int end, int y)
{
if(isMarkerHighlightEnabled())
{
Buffer buffer = textArea.getBuffer();
if(buffer.getMarkerInRange(start,end) != null)
{
gfx.setColor(getMarkerHighlightColor());
FontMetrics fm = textArea.getPainter().getFontMetrics();
gfx.fillRect(0,y,textArea.getGutter()
.getWidth(),fm.getHeight());
}
}
} //}}}
//{{{ getToolTipText() method
public String getToolTipText(int x, int y)
{
if(isMarkerHighlightEnabled())
{
int lineHeight = textArea.getPainter().getFontMetrics().getHeight();
if(lineHeight == 0)
return null;
int line = y / lineHeight;
int start = textArea.getScreenLineStartOffset(line);
int end = textArea.getScreenLineEndOffset(line);
if(start == -1 || end == -1)
return null;
Marker marker = textArea.getBuffer().getMarkerInRange(start,end);
if(marker != null)
{
char shortcut = marker.getShortcut();
if(shortcut == '\0')
return jEdit.getProperty("view.gutter.marker.no-name");
else
{
String[] args = { String.valueOf(shortcut) };
return jEdit.getProperty("view.gutter.marker",args);
}
}
}
return null;
} //}}}
} //}}}
}
|
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.