1 /******************************************************************************* 2 * Copyright (c) 2006, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Tom Eicher (Avaloq Evolution AG) - block selection mode 14 *******************************************************************************/ 15 package org.eclipse.jface.text; 16 17 import org.eclipse.swt.custom.StyledText; 18 import org.eclipse.swt.graphics.GC; 19 import org.eclipse.swt.graphics.Point; 20 import org.eclipse.swt.graphics.Rectangle; 21 import org.eclipse.swt.widgets.Control; 22 import org.eclipse.swt.widgets.Display; 23 24 import org.eclipse.jface.internal.text.SelectionProcessor; 25 26 import org.eclipse.jface.text.source.ILineRange; 27 import org.eclipse.jface.text.source.LineRange; 28 29 /** 30 * A collection of JFace Text functions. 31 * <p> 32 * This class is neither intended to be instantiated nor subclassed. 33 * </p> 34 * 35 * @since 3.3 36 * @noinstantiate This class is not intended to be instantiated by clients. 37 */ 38 public final class JFaceTextUtil { 39 JFaceTextUtil()40 private JFaceTextUtil() { 41 // Do not instantiate 42 } 43 44 /** 45 * Computes the full line height for the text line corresponding to the given widget line, 46 * considering the possible line wrapping. 47 * 48 * @param styledText the widget 49 * @param widgetLine the widget line 50 * @return the full real height of the corresponding line of text (which might wrap to multiple 51 * widget lines) in the widget 52 * @since 3.11 53 */ computeLineHeight(StyledText styledText, int widgetLine)54 public static int computeLineHeight(StyledText styledText, int widgetLine) { 55 boolean isWrapActive= styledText.getWordWrap(); 56 int lineHeight; 57 int offset= styledText.getOffsetAtLine(widgetLine); 58 if (!isWrapActive) { 59 lineHeight= styledText.getLineHeight(offset); 60 } else { 61 int offsetEnd= offset + styledText.getLine(widgetLine).length(); 62 if (offsetEnd == styledText.getCharCount()) { 63 lineHeight= styledText.getLineHeight(offset); 64 } else { 65 Rectangle textBounds= styledText.getTextBounds(offset, offsetEnd); 66 lineHeight= textBounds.height; 67 } 68 } 69 return lineHeight; 70 } 71 72 /** 73 * Computes the line height for the given line range. 74 * 75 * @param textWidget the <code>StyledText</code> widget 76 * @param startLine the start line 77 * @param endLine the end line (exclusive) 78 * @param lineCount the line count used by the old API 79 * @return the height of all lines starting with <code>startLine</code> and ending above <code>endLime</code> 80 */ computeLineHeight(StyledText textWidget, int startLine, int endLine, int lineCount)81 public static int computeLineHeight(StyledText textWidget, int startLine, int endLine, int lineCount) { 82 return getLinePixel(textWidget, endLine) - getLinePixel(textWidget, startLine); 83 } 84 85 /** 86 * Returns the last fully visible line of the widget. The exact semantics of "last fully visible 87 * line" are: 88 * <ul> 89 * <li>the last line of which the last pixel is visible, if any 90 * <li>otherwise, the only line that is partially visible 91 * </ul> 92 * 93 * @param widget the widget 94 * @return the last fully visible line 95 */ getBottomIndex(StyledText widget)96 public static int getBottomIndex(StyledText widget) { 97 int lastPixel= computeLastVisiblePixel(widget); 98 99 // bottom is in [0 .. lineCount - 1] 100 int bottom= widget.getLineIndex(lastPixel); 101 102 // bottom is the first line - no more checking 103 if (bottom == 0) 104 return bottom; 105 106 int pixel= widget.getLinePixel(bottom); 107 // bottom starts on or before the client area start - bottom is the only visible line 108 if (pixel <= 0) 109 return bottom; 110 111 int height= computeLineHeight(widget, bottom); 112 113 // bottom is not showing entirely - use the previous line 114 if (pixel + height - 1 > lastPixel) 115 return bottom - 1; 116 117 // bottom is fully visible and its last line is exactly the last pixel 118 return bottom; 119 } 120 121 /** 122 * Returns the index of the first (possibly only partially) visible line of the widget 123 * 124 * @param widget the widget 125 * @return the index of the first line of which a pixel is visible 126 */ getPartialTopIndex(StyledText widget)127 public static int getPartialTopIndex(StyledText widget) { 128 // see StyledText#getPartialTopIndex() 129 int top= widget.getTopIndex(); 130 int pixels= widget.getLinePixel(top); 131 132 if (pixels > 0) 133 top--; 134 135 return top; 136 } 137 138 /** 139 * Returns the index of the last (possibly only partially) visible line of the widget 140 * 141 * @param widget the text widget 142 * @return the index of the last line of which a pixel is visible 143 */ getPartialBottomIndex(StyledText widget)144 public static int getPartialBottomIndex(StyledText widget) { 145 // @see StyledText#getPartialBottomIndex() 146 int lastPixel= computeLastVisiblePixel(widget); 147 int bottom= widget.getLineIndex(lastPixel); 148 return bottom; 149 } 150 151 /** 152 * Returns the last visible pixel in the widget's client area. 153 * 154 * @param widget the widget 155 * @return the last visible pixel in the widget's client area 156 */ computeLastVisiblePixel(StyledText widget)157 private static int computeLastVisiblePixel(StyledText widget) { 158 int caHeight= widget.getClientArea().height; 159 int lastPixel= caHeight - 1; 160 // XXX: what if there is a margin? can't take trim as this includes the scrollbars which are not part of the client area 161 // if ((textWidget.getStyle() & SWT.BORDER) != 0) 162 // lastPixel -= 4; 163 return lastPixel; 164 } 165 166 /** 167 * Returns the line index of the first visible model line in the viewer. The line may be only 168 * partially visible. 169 * 170 * @param viewer the text viewer 171 * @return the first line of which a pixel is visible, or -1 for no line 172 */ getPartialTopIndex(ITextViewer viewer)173 public static int getPartialTopIndex(ITextViewer viewer) { 174 StyledText widget= viewer.getTextWidget(); 175 int widgetTop= getPartialTopIndex(widget); 176 return widgetLine2ModelLine(viewer, widgetTop); 177 } 178 179 /** 180 * Returns the last, possibly partially, visible line in the view port. 181 * 182 * @param viewer the text viewer 183 * @return the last, possibly partially, visible line in the view port 184 */ getPartialBottomIndex(ITextViewer viewer)185 public static int getPartialBottomIndex(ITextViewer viewer) { 186 StyledText textWidget= viewer.getTextWidget(); 187 int widgetBottom= getPartialBottomIndex(textWidget); 188 return widgetLine2ModelLine(viewer, widgetBottom); 189 } 190 191 /** 192 * Returns the range of lines that is visible in the viewer, including any partially visible 193 * lines. 194 * 195 * @param viewer the viewer 196 * @return the range of lines that is visible in the viewer, <code>null</code> if no lines are 197 * visible 198 */ getVisibleModelLines(ITextViewer viewer)199 public static ILineRange getVisibleModelLines(ITextViewer viewer) { 200 int top= getPartialTopIndex(viewer); 201 int bottom= getPartialBottomIndex(viewer); 202 if (top == -1 || bottom == -1) 203 return null; 204 return new LineRange(top, bottom - top + 1); 205 } 206 207 /** 208 * Converts a widget line into a model (i.e. {@link IDocument}) line using the 209 * {@link ITextViewerExtension5} if available, otherwise by adapting the widget line to the 210 * viewer's {@link ITextViewer#getVisibleRegion() visible region}. 211 * 212 * @param viewer the viewer 213 * @param widgetLine the widget line to convert. 214 * @return the model line corresponding to <code>widgetLine</code> or -1 to signal that there 215 * is no corresponding model line 216 */ widgetLine2ModelLine(ITextViewer viewer, int widgetLine)217 public static int widgetLine2ModelLine(ITextViewer viewer, int widgetLine) { 218 int modelLine; 219 if (viewer instanceof ITextViewerExtension5) { 220 ITextViewerExtension5 extension= (ITextViewerExtension5) viewer; 221 modelLine= extension.widgetLine2ModelLine(widgetLine); 222 } else { 223 try { 224 IRegion r= viewer.getVisibleRegion(); 225 IDocument d= viewer.getDocument(); 226 if (d == null) 227 return -1; 228 modelLine= widgetLine + d.getLineOfOffset(r.getOffset()); 229 } catch (BadLocationException x) { 230 modelLine= widgetLine; 231 } 232 } 233 return modelLine; 234 } 235 236 /** 237 * Converts a model (i.e. {@link IDocument}) line into a widget line using the 238 * {@link ITextViewerExtension5} if available, otherwise by adapting the model line to the 239 * viewer's {@link ITextViewer#getVisibleRegion() visible region}. 240 * 241 * @param viewer the viewer 242 * @param modelLine the model line to convert. 243 * @return the widget line corresponding to <code>modelLine</code> or -1 to signal that there 244 * is no corresponding widget line 245 */ modelLineToWidgetLine(ITextViewer viewer, final int modelLine)246 public static int modelLineToWidgetLine(ITextViewer viewer, final int modelLine) { 247 int widgetLine; 248 if (viewer instanceof ITextViewerExtension5) { 249 ITextViewerExtension5 extension= (ITextViewerExtension5) viewer; 250 widgetLine= extension.modelLine2WidgetLine(modelLine); 251 } else { 252 IRegion region= viewer.getVisibleRegion(); 253 IDocument document= viewer.getDocument(); 254 if (document == null) 255 return -1; 256 try { 257 int visibleStartLine= document.getLineOfOffset(region.getOffset()); 258 int visibleEndLine= document.getLineOfOffset(region.getOffset() + region.getLength()); 259 if (modelLine < visibleStartLine || modelLine > visibleEndLine) 260 widgetLine= -1; 261 else 262 widgetLine= modelLine - visibleStartLine; 263 } catch (BadLocationException x) { 264 // ignore and return -1 265 widgetLine= -1; 266 } 267 } 268 return widgetLine; 269 } 270 271 272 /** 273 * Returns the number of hidden pixels of the first partially visible line. If there is no 274 * partially visible line, zero is returned. 275 * 276 * @param textWidget the widget 277 * @return the number of hidden pixels of the first partial line, always >= 0 278 */ getHiddenTopLinePixels(StyledText textWidget)279 public static int getHiddenTopLinePixels(StyledText textWidget) { 280 int top= getPartialTopIndex(textWidget); 281 return -textWidget.getLinePixel(top); 282 } 283 284 /* 285 * @see StyledText#getLinePixel(int) 286 */ getLinePixel(StyledText textWidget, int line)287 public static int getLinePixel(StyledText textWidget, int line) { 288 return textWidget.getLinePixel(line); 289 } 290 291 /* 292 * @see StyledText#getLineIndex(int) 293 */ getLineIndex(StyledText textWidget, int y)294 public static int getLineIndex(StyledText textWidget, int y) { 295 int lineIndex= textWidget.getLineIndex(y); 296 return lineIndex; 297 } 298 299 /** 300 * Returns <code>true</code> if the widget displays the entire contents, i.e. it cannot 301 * be vertically scrolled. 302 * 303 * @param widget the widget 304 * @return <code>true</code> if the widget displays the entire contents, i.e. it cannot 305 * be vertically scrolled, <code>false</code> otherwise 306 */ isShowingEntireContents(StyledText widget)307 public static boolean isShowingEntireContents(StyledText widget) { 308 if (widget.getTopPixel() != 0) { 309 // more efficient shortcut 310 return false; 311 } 312 313 int lastVisiblePixel= computeLastVisiblePixel(widget); 314 int bottom= widget.getLineIndex(lastVisiblePixel); 315 if (bottom + 1 < widget.getLineCount()) { 316 // There's definitely more lines below 317 return false; 318 } 319 320 // Check whether the last line is fully visible 321 int bottomLastPixel= getLinePixel(widget, bottom + 1) - 1; 322 return bottomLastPixel <= lastVisiblePixel; 323 } 324 325 /** 326 * Determines the graphical area covered by the given text region in 327 * the given viewer. 328 * 329 * @param region the region whose graphical extend must be computed 330 * @param textViewer the text viewer containing the region 331 * @return the graphical extend of the given region in the given viewer 332 * 333 * @since 3.4 334 */ computeArea(IRegion region, ITextViewer textViewer)335 public static Rectangle computeArea(IRegion region, ITextViewer textViewer) { 336 int start= 0; 337 int end= 0; 338 IRegion widgetRegion= modelRange2WidgetRange(region, textViewer); 339 if (widgetRegion != null) { 340 start= widgetRegion.getOffset(); 341 end= start + widgetRegion.getLength(); 342 } 343 344 StyledText styledText= textViewer.getTextWidget(); 345 Rectangle bounds; 346 if (end > 0 && start < end) 347 bounds= styledText.getTextBounds(start, end - 1); 348 else { 349 Point loc= styledText.getLocationAtOffset(start); 350 bounds= new Rectangle(loc.x, loc.y, getAverageCharWidth(textViewer.getTextWidget()), 351 computeLineHeight(styledText, styledText.getLineAtOffset(start))); 352 } 353 354 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); 355 } 356 357 /** 358 * Translates a given region of the text viewer's document into 359 * the corresponding region of the viewer's widget. 360 * 361 * @param region the document region 362 * @param textViewer the viewer containing the region 363 * @return the corresponding widget region 364 * 365 * @since 3.4 366 */ modelRange2WidgetRange(IRegion region, ITextViewer textViewer)367 private static IRegion modelRange2WidgetRange(IRegion region, ITextViewer textViewer) { 368 if (textViewer instanceof ITextViewerExtension5) { 369 ITextViewerExtension5 extension= (ITextViewerExtension5) textViewer; 370 return extension.modelRange2WidgetRange(region); 371 } 372 373 IRegion visibleRegion= textViewer.getVisibleRegion(); 374 int start= region.getOffset() - visibleRegion.getOffset(); 375 int end= start + region.getLength(); 376 if (end > visibleRegion.getLength()) 377 end= visibleRegion.getLength(); 378 379 return new Region(start, end - start); 380 } 381 382 /** 383 * Returns the average character width of the given control's font. 384 * 385 * @param control the control to calculate the average char width for 386 * @return the average character width of the controls font 387 * 388 * @since 3.4 389 */ getAverageCharWidth(Control control)390 public static int getAverageCharWidth(Control control) { 391 GC gc= new GC(control); 392 gc.setFont(control.getFont()); 393 double increment= gc.getFontMetrics().getAverageCharacterWidth(); 394 gc.dispose(); 395 return (int) increment; 396 } 397 398 /** 399 * Returns <code>true</code> if the text covered by <code>selection</code> does not contain any 400 * characters in the given viewer. Note the difference to {@link ITextSelection#isEmpty()}, 401 * which returns <code>true</code> only for invalid selections. 402 * 403 * @param viewer the viewer 404 * @param selection the selection 405 * @return <code>true</code> if <code>selection</code> does not contain any text, 406 * <code>false</code> otherwise 407 * @throws BadLocationException if accessing the document failed 408 * @since 3.5 409 */ isEmpty(ITextViewer viewer, ITextSelection selection)410 public static boolean isEmpty(ITextViewer viewer, ITextSelection selection) throws BadLocationException { 411 return new SelectionProcessor(viewer).isEmpty(selection); 412 } 413 414 /** 415 * Returns the text regions covered by the given selection in the given viewer. 416 * 417 * @param viewer the viewer 418 * @param selection the selection 419 * @return the text regions corresponding to <code>selection</code> 420 * @throws BadLocationException if accessing the document failed 421 * @since 3.5 422 */ getCoveredRanges(ITextViewer viewer, ITextSelection selection)423 public static IRegion[] getCoveredRanges(ITextViewer viewer, ITextSelection selection) throws BadLocationException { 424 return new SelectionProcessor(viewer).getRanges(selection); 425 } 426 427 /** 428 * Returns the offset in the given viewer that corresponds to the current cursor location. 429 * 430 * @param viewer the viewer 431 * @return the offset for the current cursor location or -1 if not available 432 * @since 3.5 433 */ getOffsetForCursorLocation(ITextViewer viewer)434 public static int getOffsetForCursorLocation(ITextViewer viewer) { 435 try { 436 StyledText text= viewer.getTextWidget(); 437 if (text == null || text.isDisposed()) { 438 return -1; 439 } 440 441 Display display= text.getDisplay(); 442 Point absolutePosition= display.getCursorLocation(); 443 Point relativePosition= text.toControl(absolutePosition); 444 445 int widgetOffset= text.getOffsetAtPoint(relativePosition); 446 if (widgetOffset == -1) { 447 return -1; 448 } 449 Point p= text.getLocationAtOffset(widgetOffset); 450 if (p.x > relativePosition.x) { 451 widgetOffset--; 452 } 453 454 if (viewer instanceof ITextViewerExtension5) { 455 ITextViewerExtension5 extension= (ITextViewerExtension5)viewer; 456 return extension.widgetOffset2ModelOffset(widgetOffset); 457 } 458 459 return widgetOffset + viewer.getVisibleRegion().getOffset(); 460 } catch (IllegalArgumentException e) { 461 return -1; 462 } 463 } 464 } 465