blob: 4736386ab0b1b70b1bfe1bed01823f46576cb9e4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2013 IBM Corporation and others.
* 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://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Tom Eicher (Avaloq Evolution AG) - block selection mode
*******************************************************************************/
package org.eclipse.jface.text;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.jface.internal.text.SelectionProcessor;
import org.eclipse.jface.text.source.ILineRange;
import org.eclipse.jface.text.source.LineRange;
/**
* A collection of JFace Text functions.
* <p>
* This class is neither intended to be instantiated nor subclassed.
* </p>
*
* @since 3.3
* @noinstantiate This class is not intended to be instantiated by clients.
*/
public final class JFaceTextUtil {
private JFaceTextUtil() {
// Do not instantiate
}
/**
* Computes the line height for the given line range.
*
* @param textWidget the <code>StyledText</code> widget
* @param startLine the start line
* @param endLine the end line (exclusive)
* @param lineCount the line count used by the old API
* @return the height of all lines starting with <code>startLine</code> and ending above <code>endLime</code>
*/
public static int computeLineHeight(StyledText textWidget, int startLine, int endLine, int lineCount) {
return getLinePixel(textWidget, endLine) - getLinePixel(textWidget, startLine);
}
/**
* Returns the last fully visible line of the widget. The exact semantics of "last fully visible
* line" are:
* <ul>
* <li>the last line of which the last pixel is visible, if any
* <li>otherwise, the only line that is partially visible
* </ul>
*
* @param widget the widget
* @return the last fully visible line
*/
public static int getBottomIndex(StyledText widget) {
int lastPixel= computeLastVisiblePixel(widget);
// bottom is in [0 .. lineCount - 1]
int bottom= widget.getLineIndex(lastPixel);
// bottom is the first line - no more checking
if (bottom == 0)
return bottom;
int pixel= widget.getLinePixel(bottom);
// bottom starts on or before the client area start - bottom is the only visible line
if (pixel <= 0)
return bottom;
int offset= widget.getOffsetAtLine(bottom);
int height= widget.getLineHeight(offset);
// bottom is not showing entirely - use the previous line
if (pixel + height - 1 > lastPixel)
return bottom - 1;
// bottom is fully visible and its last line is exactly the last pixel
return bottom;
}
/**
* Returns the index of the first (possibly only partially) visible line of the widget
*
* @param widget the widget
* @return the index of the first line of which a pixel is visible
*/
public static int getPartialTopIndex(StyledText widget) {
// see StyledText#getPartialTopIndex()
int top= widget.getTopIndex();
int pixels= widget.getLinePixel(top);
// FIXME remove when https://bugs.eclipse.org/bugs/show_bug.cgi?id=123770 is fixed
if (pixels == -widget.getLineHeight(widget.getOffsetAtLine(top))) {
top++;
pixels= 0;
}
if (pixels > 0)
top--;
return top;
}
/**
* Returns the index of the last (possibly only partially) visible line of the widget
*
* @param widget the text widget
* @return the index of the last line of which a pixel is visible
*/
public static int getPartialBottomIndex(StyledText widget) {
// @see StyledText#getPartialBottomIndex()
int lastPixel= computeLastVisiblePixel(widget);
int bottom= widget.getLineIndex(lastPixel);
return bottom;
}
/**
* Returns the last visible pixel in the widget's client area.
*
* @param widget the widget
* @return the last visible pixel in the widget's client area
*/
private static int computeLastVisiblePixel(StyledText widget) {
int caHeight= widget.getClientArea().height;
int lastPixel= caHeight - 1;
// XXX: what if there is a margin? can't take trim as this includes the scrollbars which are not part of the client area
// if ((textWidget.getStyle() & SWT.BORDER) != 0)
// lastPixel -= 4;
return lastPixel;
}
/**
* Returns the line index of the first visible model line in the viewer. The line may be only
* partially visible.
*
* @param viewer the text viewer
* @return the first line of which a pixel is visible, or -1 for no line
*/
public static int getPartialTopIndex(ITextViewer viewer) {
StyledText widget= viewer.getTextWidget();
int widgetTop= getPartialTopIndex(widget);
return widgetLine2ModelLine(viewer, widgetTop);
}
/**
* Returns the last, possibly partially, visible line in the view port.
*
* @param viewer the text viewer
* @return the last, possibly partially, visible line in the view port
*/
public static int getPartialBottomIndex(ITextViewer viewer) {
StyledText textWidget= viewer.getTextWidget();
int widgetBottom= getPartialBottomIndex(textWidget);
return widgetLine2ModelLine(viewer, widgetBottom);
}
/**
* Returns the range of lines that is visible in the viewer, including any partially visible
* lines.
*
* @param viewer the viewer
* @return the range of lines that is visible in the viewer, <code>null</code> if no lines are
* visible
*/
public static ILineRange getVisibleModelLines(ITextViewer viewer) {
int top= getPartialTopIndex(viewer);
int bottom= getPartialBottomIndex(viewer);
if (top == -1 || bottom == -1)
return null;
return new LineRange(top, bottom - top + 1);
}
/**
* Converts a widget line into a model (i.e. {@link IDocument}) line using the
* {@link ITextViewerExtension5} if available, otherwise by adapting the widget line to the
* viewer's {@link ITextViewer#getVisibleRegion() visible region}.
*
* @param viewer the viewer
* @param widgetLine the widget line to convert.
* @return the model line corresponding to <code>widgetLine</code> or -1 to signal that there
* is no corresponding model line
*/
public static int widgetLine2ModelLine(ITextViewer viewer, int widgetLine) {
int modelLine;
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
modelLine= extension.widgetLine2ModelLine(widgetLine);
} else {
try {
IRegion r= viewer.getVisibleRegion();
IDocument d= viewer.getDocument();
if (d == null)
return -1;
modelLine= widgetLine + d.getLineOfOffset(r.getOffset());
} catch (BadLocationException x) {
modelLine= widgetLine;
}
}
return modelLine;
}
/**
* Converts a model (i.e. {@link IDocument}) line into a widget line using the
* {@link ITextViewerExtension5} if available, otherwise by adapting the model line to the
* viewer's {@link ITextViewer#getVisibleRegion() visible region}.
*
* @param viewer the viewer
* @param modelLine the model line to convert.
* @return the widget line corresponding to <code>modelLine</code> or -1 to signal that there
* is no corresponding widget line
*/
public static int modelLineToWidgetLine(ITextViewer viewer, final int modelLine) {
int widgetLine;
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
widgetLine= extension.modelLine2WidgetLine(modelLine);
} else {
IRegion region= viewer.getVisibleRegion();
IDocument document= viewer.getDocument();
if (document == null)
return -1;
try {
int visibleStartLine= document.getLineOfOffset(region.getOffset());
int visibleEndLine= document.getLineOfOffset(region.getOffset() + region.getLength());
if (modelLine < visibleStartLine || modelLine > visibleEndLine)
widgetLine= -1;
else
widgetLine= modelLine - visibleStartLine;
} catch (BadLocationException x) {
// ignore and return -1
widgetLine= -1;
}
}
return widgetLine;
}
/**
* Returns the number of hidden pixels of the first partially visible line. If there is no
* partially visible line, zero is returned.
*
* @param textWidget the widget
* @return the number of hidden pixels of the first partial line, always &gt;= 0
*/
public static int getHiddenTopLinePixels(StyledText textWidget) {
int top= getPartialTopIndex(textWidget);
return -textWidget.getLinePixel(top);
}
/*
* @see StyledText#getLinePixel(int)
*/
public static int getLinePixel(StyledText textWidget, int line) {
return textWidget.getLinePixel(line);
}
/*
* @see StyledText#getLineIndex(int)
*/
public static int getLineIndex(StyledText textWidget, int y) {
int lineIndex= textWidget.getLineIndex(y);
return lineIndex;
}
/**
* Returns <code>true</code> if the widget displays the entire contents, i.e. it cannot
* be vertically scrolled.
*
* @param widget the widget
* @return <code>true</code> if the widget displays the entire contents, i.e. it cannot
* be vertically scrolled, <code>false</code> otherwise
*/
public static boolean isShowingEntireContents(StyledText widget) {
if (widget.getTopPixel() != 0) // more efficient shortcut
return false;
int lastVisiblePixel= computeLastVisiblePixel(widget);
int lastPossiblePixel= widget.getLinePixel(widget.getLineCount());
return lastPossiblePixel <= lastVisiblePixel;
}
/**
* Determines the graphical area covered by the given text region in
* the given viewer.
*
* @param region the region whose graphical extend must be computed
* @param textViewer the text viewer containing the region
* @return the graphical extend of the given region in the given viewer
*
* @since 3.4
*/
public static Rectangle computeArea(IRegion region, ITextViewer textViewer) {
int start= 0;
int end= 0;
IRegion widgetRegion= modelRange2WidgetRange(region, textViewer);
if (widgetRegion != null) {
start= widgetRegion.getOffset();
end= start + widgetRegion.getLength();
}
StyledText styledText= textViewer.getTextWidget();
Rectangle bounds;
if (end > 0 && start < end)
bounds= styledText.getTextBounds(start, end - 1);
else {
Point loc= styledText.getLocationAtOffset(start);
bounds= new Rectangle(loc.x, loc.y, getAverageCharWidth(textViewer.getTextWidget()), styledText.getLineHeight(start));
}
return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
}
/**
* Translates a given region of the text viewer's document into
* the corresponding region of the viewer's widget.
*
* @param region the document region
* @param textViewer the viewer containing the region
* @return the corresponding widget region
*
* @since 3.4
*/
private static IRegion modelRange2WidgetRange(IRegion region, ITextViewer textViewer) {
if (textViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) textViewer;
return extension.modelRange2WidgetRange(region);
}
IRegion visibleRegion= textViewer.getVisibleRegion();
int start= region.getOffset() - visibleRegion.getOffset();
int end= start + region.getLength();
if (end > visibleRegion.getLength())
end= visibleRegion.getLength();
return new Region(start, end - start);
}
/**
* Returns the average character width of the given control's font.
*
* @param control the control to calculate the average char width for
* @return the average character width of the controls font
*
* @since 3.4
*/
public static int getAverageCharWidth(Control control) {
GC gc= new GC(control);
gc.setFont(control.getFont());
int increment= gc.getFontMetrics().getAverageCharWidth();
gc.dispose();
return increment;
}
/**
* Returns <code>true</code> if the text covered by <code>selection</code> does not contain any
* characters in the given viewer. Note the difference to {@link ITextSelection#isEmpty()},
* which returns <code>true</code> only for invalid selections.
*
* @param viewer the viewer
* @param selection the selection
* @return <code>true</code> if <code>selection</code> does not contain any text,
* <code>false</code> otherwise
* @throws BadLocationException if accessing the document failed
* @since 3.5
*/
public static boolean isEmpty(ITextViewer viewer, ITextSelection selection) throws BadLocationException {
return new SelectionProcessor(viewer).isEmpty(selection);
}
/**
* Returns the text regions covered by the given selection in the given viewer.
*
* @param viewer the viewer
* @param selection the selection
* @return the text regions corresponding to <code>selection</code>
* @throws BadLocationException if accessing the document failed
* @since 3.5
*/
public static IRegion[] getCoveredRanges(ITextViewer viewer, ITextSelection selection) throws BadLocationException {
return new SelectionProcessor(viewer).getRanges(selection);
}
/**
* Returns the offset in the given viewer that corresponds to the current cursor location.
*
* @param viewer the viewer
* @return the offset for the current cursor location or -1 if not available
* @since 3.5
*/
public static int getOffsetForCursorLocation(ITextViewer viewer) {
try {
StyledText text= viewer.getTextWidget();
if (text == null || text.isDisposed())
return -1;
Display display= text.getDisplay();
Point absolutePosition= display.getCursorLocation();
Point relativePosition= text.toControl(absolutePosition);
int widgetOffset= text.getOffsetAtLocation(relativePosition);
Point p= text.getLocationAtOffset(widgetOffset);
if (p.x > relativePosition.x)
widgetOffset--;
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5)viewer;
return extension.widgetOffset2ModelOffset(widgetOffset);
}
return widgetOffset + viewer.getVisibleRegion().getOffset();
} catch (IllegalArgumentException e) {
return -1;
}
}
}