Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 2421c354d837306ea417bcc6b4586335f71a4805 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
/*******************************************************************************
 * Copyright (c) 2006, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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 full line height for the text line corresponding to the given widget line,
	 * considering the possible line wrapping.
	 *
	 * @param styledText the widget
	 * @param widgetLine the widget line
	 * @return the full real height of the corresponding line of text (which might wrap to multiple
	 *         widget lines) in the widget
	 * @since 3.11
	 */
	public static int computeLineHeight(StyledText styledText, int widgetLine) {
		boolean isWrapActive= styledText.getWordWrap();
		int lineHeight;
		int offset= styledText.getOffsetAtLine(widgetLine);
		if (!isWrapActive) {
			lineHeight= styledText.getLineHeight(offset);
		} else {
			int offsetEnd= offset + styledText.getLine(widgetLine).length();
			if (offsetEnd == styledText.getCharCount()) {
				lineHeight= styledText.getLineHeight(offset);
			} else {
				Rectangle textBounds= styledText.getTextBounds(offset, offsetEnd);
				lineHeight= textBounds.height;
			}
		}
		return lineHeight;
	}

	/**
	 * 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 height= computeLineHeight(widget, bottom);

		// 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);

		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 bottom= widget.getLineIndex(lastVisiblePixel);
		if (bottom + 1 < widget.getLineCount()) {
			// There's definitely more lines below
			return false;
		}

		// Check whether the last line is fully visible
		int bottomLastPixel= getLinePixel(widget, bottom + 1) - 1;
		return bottomLastPixel <= 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()),
					computeLineHeight(styledText, styledText.getLineAtOffset(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());
		double increment= gc.getFontMetrics().getAverageCharacterWidth();
		gc.dispose();
		return (int) 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.getOffsetAtPoint(relativePosition);
			if (widgetOffset == -1) {
				return -1;
			}
			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;
		}
	}
}

Back to the top