| /******************************************************************************* |
| * Copyright (c) 2000, 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 |
| * Nikolay Botev <bono8106@hotmail.com> - [rulers] Shift clicking in line number column doesn't select range - https://bugs.eclipse.org/bugs/show_bug.cgi?id=32166 |
| * Nikolay Botev <bono8106@hotmail.com> - [rulers] Clicking in line number ruler should not trigger annotation ruler - https://bugs.eclipse.org/bugs/show_bug.cgi?id=40889 |
| * Florian Weßling <flo@cdhq.de> - [rulers] Line numbering was wrong when word wrap was active - https://bugs.eclipse.org/bugs/show_bug.cgi?id=35779 |
| * Rüdiger Herrmann - Insufficient is-disposed check in LineNumberRulerColumn::redraw - https://bugs.eclipse.org/bugs/show_bug.cgi?id=506427 |
| * Angelo ZERR <angelo.zerr@gmail.com> - Compute line height by taking care of line spacing - https://bugs.eclipse.org/bugs/show_bug.cgi?id=481968 |
| *******************************************************************************/ |
| package org.eclipse.jface.text.source; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.Arrays; |
| import java.util.function.Consumer; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.MouseMoveListener; |
| import org.eclipse.swt.events.MouseWheelListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.graphics.FontMetrics; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.ImageData; |
| import org.eclipse.swt.graphics.ImageDataProvider; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Canvas; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.TypedListener; |
| |
| import org.eclipse.jface.util.Util; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextListener; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.ITextViewerExtension; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.JFaceTextUtil; |
| import org.eclipse.jface.text.TextEvent; |
| |
| |
| /** |
| * A vertical ruler column displaying line numbers. |
| * Clients usually instantiate and configure object of this class. |
| * |
| * @since 2.0 |
| */ |
| public class LineNumberRulerColumn implements IVerticalRulerColumn { |
| |
| /** |
| * <code>true</code> if we're on a Mac, where drawing on an Image currently only draws at 100% zoom level, |
| * which results in blurry line numbers on a Retina display. |
| * |
| * @see <a href="https://bugs.eclipse.org/516293">bug 516293</a> |
| * @since 3.6 |
| */ |
| private final boolean IS_MAC_BUG_516293= Util.isMac() |
| && !"false".equals(System.getProperty("LineNumberRulerColumn.retina.workaround")) //$NON-NLS-1$ //$NON-NLS-2$ |
| && internalSupportsZoomedPaint(); |
| |
| /** |
| * Internal listener class. |
| */ |
| class InternalListener implements ITextListener { |
| |
| /** |
| * @since 3.1 |
| */ |
| private boolean fCachedRedrawState= true; |
| |
| @Override |
| public void textChanged(TextEvent event) { |
| |
| fCachedRedrawState= event.getViewerRedrawState(); |
| if (!fCachedRedrawState) |
| return; |
| |
| if (updateNumberOfDigits()) { |
| computeIndentations(); |
| layout(event.getViewerRedrawState()); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Handles all the mouse interaction in this line number ruler column. |
| */ |
| class MouseHandler implements MouseListener, MouseMoveListener, MouseWheelListener { |
| |
| /** The cached view port size. */ |
| private int fCachedViewportSize; |
| /** The area of the line at which line selection started. */ |
| private int fStartLineOffset; |
| /** The number of the line at which line selection started. */ |
| private int fStartLineNumber; |
| /** The auto scroll direction. */ |
| private int fAutoScrollDirection; |
| /* @since 3.2 */ |
| private boolean fIsListeningForMove= false; |
| |
| @Override |
| public void mouseUp(MouseEvent event) { |
| // see bug 45700 |
| if (event.button == 1) { |
| stopSelecting(); |
| stopAutoScroll(); |
| } |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent event) { |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| // see bug 45700 |
| if (event.button == 1) { |
| startSelecting((event.stateMask & SWT.SHIFT) != 0); |
| } |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent event) { |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| stopSelecting(); |
| stopAutoScroll(); |
| } |
| |
| @Override |
| public void mouseMove(MouseEvent event) { |
| if (fIsListeningForMove && !autoScroll(event)) { |
| int newLine= fParentRuler.toDocumentLineNumber(event.y); |
| expandSelection(newLine); |
| } |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| } |
| |
| /** |
| * Called when line drag selection started. Adds mouse move and track |
| * listeners to this column's control. |
| * |
| * @param expandExistingSelection if <code>true</code> the existing selection will be expanded, |
| * otherwise a new selection is started |
| */ |
| private void startSelecting(boolean expandExistingSelection) { |
| try { |
| |
| // select line |
| IDocument document= fCachedTextViewer.getDocument(); |
| int lineNumber= fParentRuler.getLineOfLastMouseButtonActivity(); |
| final StyledText textWidget= fCachedTextViewer.getTextWidget(); |
| if (textWidget != null && !textWidget.isFocusControl()) |
| textWidget.setFocus(); |
| if (expandExistingSelection && fCachedTextViewer instanceof ITextViewerExtension5 && textWidget != null) { |
| ITextViewerExtension5 extension5= ((ITextViewerExtension5)fCachedTextViewer); |
| // Find model cursor position |
| int widgetCaret= textWidget.getCaretOffset(); |
| int modelCaret= extension5.widgetOffset2ModelOffset(widgetCaret); |
| // Find model selection range |
| Point selection= fCachedTextViewer.getSelectedRange(); |
| // Start from tail of selection range (opposite of cursor position) |
| int startOffset= modelCaret == selection.x ? selection.x + selection.y : selection.x; |
| |
| fStartLineNumber= document.getLineOfOffset(startOffset); |
| fStartLineOffset= startOffset; |
| |
| expandSelection(lineNumber); |
| } else { |
| fStartLineNumber= lineNumber; |
| fStartLineOffset= document.getLineInformation(fStartLineNumber).getOffset(); |
| Point currentSelection= fCachedTextViewer.getSelectedRange(); |
| // avoid sending unnecessary selection event, see https://bugs.eclipse.org/483747 |
| if (currentSelection.x != fStartLineOffset || currentSelection.y != 0) { |
| fCachedTextViewer.setSelectedRange(fStartLineOffset, 0); |
| } |
| } |
| fCachedViewportSize= getVisibleLinesInViewport(); |
| |
| // prepare for drag selection |
| fIsListeningForMove= true; |
| |
| } catch (BadLocationException x) { |
| } |
| } |
| |
| /** |
| * Called when line drag selection stopped. Removes all previously |
| * installed listeners from this column's control. |
| */ |
| private void stopSelecting() { |
| // drag selection stopped |
| fIsListeningForMove= false; |
| } |
| |
| /** |
| * Expands the line selection from the remembered start line to the |
| * given line. |
| * |
| * @param lineNumber the line to which to expand the selection |
| */ |
| private void expandSelection(int lineNumber) { |
| try { |
| |
| IDocument document= fCachedTextViewer.getDocument(); |
| IRegion lineInfo= document.getLineInformation(lineNumber); |
| |
| Display display= fCachedTextWidget.getDisplay(); |
| Point absolutePosition= display.getCursorLocation(); |
| Point relativePosition= fCachedTextWidget.toControl(absolutePosition); |
| |
| int offset; |
| |
| if (relativePosition.x < 0) |
| offset= lineInfo.getOffset(); |
| else { |
| int widgetOffset= fCachedTextWidget.getOffsetAtPoint(relativePosition); |
| if (widgetOffset != -1) { |
| Point p= fCachedTextWidget.getLocationAtOffset(widgetOffset); |
| if (p.x > relativePosition.x) { |
| widgetOffset--; |
| } |
| |
| // Convert to model offset |
| if (fCachedTextViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5)fCachedTextViewer; |
| offset= extension.widgetOffset2ModelOffset(widgetOffset); |
| } else { |
| offset= widgetOffset + fCachedTextViewer.getVisibleRegion().getOffset(); |
| } |
| } else { |
| int lineEndOffset= lineInfo.getOffset() + lineInfo.getLength(); |
| |
| // Convert to widget offset |
| int lineEndWidgetOffset; |
| if (fCachedTextViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5)fCachedTextViewer; |
| lineEndWidgetOffset= extension.modelOffset2WidgetOffset(lineEndOffset); |
| } else |
| lineEndWidgetOffset= lineEndOffset - fCachedTextViewer.getVisibleRegion().getOffset(); |
| |
| Point p= fCachedTextWidget.getLocationAtOffset(lineEndWidgetOffset); |
| if (p.x < relativePosition.x) |
| offset= lineEndOffset; |
| else |
| offset= lineInfo.getOffset(); |
| } |
| } |
| |
| int start= Math.min(fStartLineOffset, offset); |
| int end= Math.max(fStartLineOffset, offset); |
| |
| if (lineNumber < fStartLineNumber) |
| fCachedTextViewer.setSelectedRange(end, start - end); |
| else |
| fCachedTextViewer.setSelectedRange(start, end - start); |
| |
| } catch (BadLocationException x) { |
| } |
| } |
| |
| /** |
| * Called when auto scrolling stopped. Clears the auto scroll direction. |
| */ |
| private void stopAutoScroll() { |
| fAutoScrollDirection= SWT.NULL; |
| } |
| |
| /** |
| * Called on drag selection. |
| * |
| * @param event the mouse event caught by the mouse move listener |
| * @return <code>true</code> if scrolling happened, <code>false</code> otherwise |
| */ |
| private boolean autoScroll(MouseEvent event) { |
| Rectangle area= fCanvas.getClientArea(); |
| |
| if (event.y > area.height) { |
| autoScroll(SWT.DOWN); |
| return true; |
| } |
| |
| if (event.y < 0) { |
| autoScroll(SWT.UP); |
| return true; |
| } |
| |
| stopAutoScroll(); |
| return false; |
| } |
| |
| /** |
| * Scrolls the viewer into the given direction. |
| * |
| * @param direction the scroll direction |
| */ |
| private void autoScroll(int direction) { |
| |
| if (fAutoScrollDirection == direction) |
| return; |
| |
| final int TIMER_INTERVAL= 5; |
| final Display display= fCanvas.getDisplay(); |
| Runnable timer= null; |
| switch (direction) { |
| case SWT.UP: |
| timer= new Runnable() { |
| @Override |
| public void run() { |
| if (fAutoScrollDirection == SWT.UP) { |
| int top= getInclusiveTopIndex(); |
| if (top > 0) { |
| fCachedTextViewer.setTopIndex(top -1); |
| expandSelection(top -1); |
| display.timerExec(TIMER_INTERVAL, this); |
| } |
| } |
| } |
| }; |
| break; |
| case SWT.DOWN: |
| timer= new Runnable() { |
| @Override |
| public void run() { |
| if (fAutoScrollDirection == SWT.DOWN) { |
| int top= getInclusiveTopIndex(); |
| fCachedTextViewer.setTopIndex(top +1); |
| expandSelection(top +1 + fCachedViewportSize); |
| display.timerExec(TIMER_INTERVAL, this); |
| } |
| } |
| }; |
| break; |
| } |
| |
| if (timer != null) { |
| fAutoScrollDirection= direction; |
| display.timerExec(TIMER_INTERVAL, timer); |
| } |
| } |
| |
| /** |
| * Returns the viewer's first visible line, even if only partially visible. |
| * |
| * @return the viewer's first visible line |
| */ |
| private int getInclusiveTopIndex() { |
| if (fCachedTextWidget != null && !fCachedTextWidget.isDisposed()) { |
| return JFaceTextUtil.getPartialTopIndex(fCachedTextViewer); |
| } |
| return -1; |
| } |
| |
| @Override |
| public void mouseScrolled(MouseEvent e) { |
| handleMouseScrolled(e); |
| } |
| } |
| |
| /** This column's parent ruler */ |
| private CompositeRuler fParentRuler; |
| /** Cached text viewer */ |
| private ITextViewer fCachedTextViewer; |
| /** Cached text widget */ |
| private StyledText fCachedTextWidget; |
| /** The columns canvas */ |
| private Canvas fCanvas; |
| /** The drawable for double buffering */ |
| private Image fBuffer; |
| /** The internal listener */ |
| private ITextListener fInternalListener= new InternalListener(); |
| /** The font of this column */ |
| private Font fFont; |
| /** The indentation cache */ |
| private int[] fIndentation; |
| /** The foreground color */ |
| private Color fForeground; |
| /** The background color */ |
| private Color fBackground; |
| /** Cached number of displayed digits */ |
| private int fCachedNumberOfDigits= -1; |
| /** Flag indicating whether a relayout is required */ |
| private boolean fRelayoutRequired= false; |
| /** |
| * Redraw runnable lock |
| * @since 3.0 |
| */ |
| private Object fRunnableLock= new Object(); |
| /** |
| * Redraw runnable state |
| * @since 3.0 |
| */ |
| private boolean fIsRunnablePosted= false; |
| /** |
| * Redraw runnable |
| * @since 3.0 |
| */ |
| private Runnable fRunnable= () -> { |
| synchronized (fRunnableLock) { |
| fIsRunnablePosted= false; |
| } |
| redraw(); |
| }; |
| /* @since 3.2 */ |
| private MouseHandler fMouseHandler; |
| /* |
| * Zoom level and cached font for the current painting operation. Workaround for bug 516293. |
| * @since 3.12 |
| */ |
| int fZoom= 100; |
| private WeakReference<Font> fLastFont; |
| private Font fLastZoomedFont; |
| |
| /** |
| * Redraw the ruler handler called when a line height change. |
| * |
| * @since 3.13 |
| */ |
| private Consumer<StyledText> lineHeightChangeHandler= (t) -> postRedraw(); |
| |
| /** |
| * Constructs a new vertical ruler column. |
| */ |
| public LineNumberRulerColumn() { |
| } |
| |
| /** |
| * Sets the foreground color of this column. |
| * |
| * @param foreground the foreground color |
| */ |
| public void setForeground(Color foreground) { |
| fForeground= foreground; |
| } |
| |
| /** |
| * Returns the foreground color being used to print the line numbers. |
| * |
| * @return the configured foreground color |
| * @since 3.0 |
| */ |
| protected Color getForeground() { |
| return fForeground; |
| } |
| |
| /** |
| * Sets the background color of this column. |
| * |
| * @param background the background color |
| */ |
| public void setBackground(Color background) { |
| fBackground= background; |
| if (fCanvas != null && !fCanvas.isDisposed()) |
| fCanvas.setBackground(getBackground(fCanvas.getDisplay())); |
| } |
| |
| /** |
| * Returns the System background color for list widgets. |
| * |
| * @param display the display |
| * @return the System background color for list widgets |
| */ |
| protected Color getBackground(Display display) { |
| if (fBackground == null) |
| return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND); |
| return fBackground; |
| } |
| |
| @Override |
| public Control getControl() { |
| return fCanvas; |
| } |
| |
| /* |
| * @see IVerticalRuleColumnr#getWidth |
| */ |
| @Override |
| public int getWidth() { |
| return fIndentation[0]; |
| } |
| |
| /** |
| * Computes the number of digits to be displayed. Returns |
| * <code>true</code> if the number of digits changed compared |
| * to the previous call of this method. If the method is called |
| * for the first time, the return value is also <code>true</code>. |
| * |
| * @return whether the number of digits has been changed |
| * @since 3.0 |
| */ |
| protected boolean updateNumberOfDigits() { |
| if (fCachedTextViewer == null) |
| return false; |
| |
| int digits= computeNumberOfDigits(); |
| |
| if (fCachedNumberOfDigits != digits) { |
| fCachedNumberOfDigits= digits; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Does the real computation of the number of digits. Subclasses may override this method if |
| * they need extra space on the line number ruler. |
| * |
| * @return the number of digits to be displayed on the line number ruler. |
| */ |
| protected int computeNumberOfDigits() { |
| IDocument document= fCachedTextViewer.getDocument(); |
| int lines= document == null ? 0 : document.getNumberOfLines(); |
| |
| int digits= 2; |
| while (lines > Math.pow(10, digits) -1) { |
| ++digits; |
| } |
| return digits; |
| } |
| |
| /** |
| * Layouts the enclosing viewer to adapt the layout to changes of the |
| * size of the individual components. |
| * |
| * @param redraw <code>true</code> if this column can be redrawn |
| */ |
| protected void layout(boolean redraw) { |
| if (!redraw) { |
| fRelayoutRequired= true; |
| return; |
| } |
| |
| fRelayoutRequired= false; |
| if (fCachedTextViewer instanceof ITextViewerExtension) { |
| ITextViewerExtension extension= (ITextViewerExtension) fCachedTextViewer; |
| Control control= extension.getControl(); |
| if (control instanceof Composite && !control.isDisposed()) { |
| Composite composite= (Composite) control; |
| composite.layout(true); |
| } |
| } |
| } |
| |
| /** |
| * Computes the indentations for the given font and stores them in |
| * <code>fIndentation</code>. |
| */ |
| protected void computeIndentations() { |
| if (fCanvas == null || fCanvas.isDisposed()) |
| return; |
| |
| GC gc= new GC(fCanvas); |
| try { |
| |
| gc.setFont(fCanvas.getFont()); |
| |
| fIndentation= new int[fCachedNumberOfDigits + 1]; |
| |
| char[] nines= new char[fCachedNumberOfDigits]; |
| Arrays.fill(nines, '9'); |
| String nineString= new String(nines); |
| Point p= gc.stringExtent(nineString); |
| fIndentation[0]= p.x; |
| |
| for (int i= 1; i <= fCachedNumberOfDigits; i++) { |
| p= gc.stringExtent(nineString.substring(0, i)); |
| fIndentation[i]= fIndentation[0] - p.x; |
| } |
| |
| } finally { |
| gc.dispose(); |
| } |
| } |
| |
| @Override |
| public Control createControl(CompositeRuler parentRuler, Composite parentControl) { |
| |
| fParentRuler= parentRuler; |
| fCachedTextViewer= parentRuler.getTextViewer(); |
| fCachedTextWidget= fCachedTextViewer.getTextWidget(); |
| |
| // on word wrap toggle a "resized" ControlEvent is fired: suggest a redraw of the line ruler |
| fCachedTextWidget.addControlListener(new ControlAdapter() { |
| @Override |
| public void controlResized(ControlEvent e) { |
| if (fCachedTextWidget != null && fCachedTextWidget.getWordWrap()) { |
| postRedraw(); |
| } |
| } |
| }); |
| |
| // track when StyledText is redrawn to check if some line height changed. In this case, the ruler must be redrawn |
| // to draw line number with well height. |
| VisibleLinesTracker.track(fCachedTextViewer, lineHeightChangeHandler); |
| |
| fCanvas= new Canvas(parentControl, SWT.NO_FOCUS ) { |
| @Override |
| public void addMouseListener(MouseListener listener) { |
| // see bug 40889, bug 230073 and AnnotationRulerColumn#isPropagatingMouseListener() |
| if (listener == fMouseHandler) |
| super.addMouseListener(listener); |
| else { |
| TypedListener typedListener= null; |
| if (listener != null) |
| typedListener= new TypedListener(listener); |
| addListener(SWT.MouseDoubleClick, typedListener); |
| } |
| } |
| }; |
| fCanvas.setBackground(getBackground(fCanvas.getDisplay())); |
| fCanvas.setForeground(fForeground); |
| |
| fCanvas.addPaintListener(event -> { |
| if (fCachedTextViewer != null) |
| doubleBufferPaint(event.gc); |
| }); |
| |
| fCanvas.addDisposeListener(e -> { |
| handleDispose(); |
| fCachedTextViewer= null; |
| fCachedTextWidget= null; |
| }); |
| |
| fMouseHandler= new MouseHandler(); |
| fCanvas.addMouseListener(fMouseHandler); |
| fCanvas.addMouseMoveListener(fMouseHandler); |
| fCanvas.addMouseWheelListener(fMouseHandler); |
| |
| if (fCachedTextViewer != null) { |
| VisibleLinesTracker.track(fCachedTextViewer, lineHeightChangeHandler); |
| fCachedTextViewer.addTextListener(fInternalListener); |
| |
| |
| if (fFont == null) { |
| if (fCachedTextWidget != null && !fCachedTextWidget.isDisposed()) |
| fFont= fCachedTextWidget.getFont(); |
| } |
| } |
| |
| if (fFont != null) |
| fCanvas.setFont(fFont); |
| |
| updateNumberOfDigits(); |
| computeIndentations(); |
| return fCanvas; |
| } |
| |
| /** |
| * Disposes the column's resources. |
| */ |
| protected void handleDispose() { |
| |
| if (fCachedTextViewer != null) { |
| VisibleLinesTracker.untrack(fCachedTextViewer, lineHeightChangeHandler); |
| } |
| |
| if (fBuffer != null) { |
| fBuffer.dispose(); |
| fBuffer= null; |
| } |
| |
| if (fLastZoomedFont != null) { |
| fLastZoomedFont.dispose(); |
| fLastZoomedFont= null; |
| fLastFont= null; |
| } |
| } |
| |
| /** |
| * Double buffer drawing. |
| * |
| * @param dest the GC to draw into |
| */ |
| private void doubleBufferPaint(GC dest) { |
| |
| Point size= fCanvas.getSize(); |
| |
| if (size.x <= 0 || size.y <= 0) |
| return; |
| |
| if (fBuffer != null) { |
| Rectangle r= fBuffer.getBounds(); |
| if (IS_MAC_BUG_516293 || r.width != size.x || r.height != size.y) { |
| fBuffer.dispose(); |
| fBuffer= null; |
| } |
| } |
| |
| ILineRange visibleLines= JFaceTextUtil.getVisibleModelLines(fCachedTextViewer); |
| if (visibleLines == null) |
| return; |
| |
| if (IS_MAC_BUG_516293) { |
| /* FIXME: Workaround (bug 516293): |
| * Relies on SWT implementation detail that GC drawing on macOS only draws at 100% zoom level. |
| * For higher zoom levels (200%), we manually scale the font and drawing coordinates, |
| * and then use getImageData(100) to extract the high-resolution image data. */ |
| fBuffer= new Image(fCanvas.getDisplay(), (ImageDataProvider) zoom -> { |
| fZoom = zoom; |
| internalSetZoom(zoom); |
| int width= size.x * zoom / 100; |
| int height= size.y * zoom / 100; |
| Image gcImage= new Image(fCanvas.getDisplay(), width, height); |
| |
| GC gc= new GC(gcImage); |
| Font font= fCanvas.getFont(); |
| if (zoom != 100) { |
| if (fLastFont != null && font == fLastFont.get()) { |
| font= fLastZoomedFont; |
| } else { |
| fLastFont= new WeakReference<>(font); |
| FontData fontData= font.getFontData()[0]; |
| fontData.setHeight(fontData.getHeight() * zoom / 100); |
| font= new Font(font.getDevice(), fontData); |
| fLastZoomedFont= font; |
| } |
| } |
| gc.setFont(font); |
| if (fForeground != null) |
| gc.setForeground(fForeground); |
| |
| try { |
| gc.setBackground(getBackground(fCanvas.getDisplay())); |
| gc.fillRectangle(0, 0, width, height); |
| |
| doPaint(gc, visibleLines); |
| } finally { |
| gc.dispose(); |
| fZoom= 100; |
| } |
| |
| ImageData imageData= gcImage.getImageData(100); |
| gcImage.dispose(); |
| return imageData; |
| }); |
| |
| } else { |
| if (fBuffer == null) |
| fBuffer= new Image(fCanvas.getDisplay(), size.x, size.y); |
| |
| GC gc= new GC(fBuffer); |
| gc.setFont(fCanvas.getFont()); |
| if (fForeground != null) |
| gc.setForeground(fForeground); |
| |
| try { |
| gc.setBackground(getBackground(fCanvas.getDisplay())); |
| gc.fillRectangle(0, 0, size.x, size.y); |
| |
| doPaint(gc, visibleLines); |
| } finally { |
| gc.dispose(); |
| } |
| } |
| |
| dest.drawImage(fBuffer, 0, 0); |
| } |
| |
| /** |
| * This method is not API and it is expected to disappear in Eclipse 4.8. |
| * Subclasses that want to take advantage of the unsupported workaround for bug 516258 |
| * can re-implement this method and return true. |
| * |
| * @return true iff this class supports the workaround for bug 516258 |
| * |
| * @nooverride This method is not intended to be re-implemented or extended by clients. |
| * @noreference This method is not intended to be referenced by clients. |
| * @since 3.12 |
| */ |
| protected boolean internalSupportsZoomedPaint() { |
| return getClass().getPackage().equals(LineNumberChangeRulerColumn.class.getPackage()); |
| } |
| |
| /** |
| * This method is not API and it is expected to disappear in Eclipse 4.8. |
| * Subclasses that want to take advantage of the unsupported workaround for bug 516258 |
| * can override this method and store the given zoom level for later use. |
| * |
| * @param zoom the zoom level to use for drawing operations |
| * |
| * @nooverride This method is not intended to be re-implemented or extended by clients. |
| * @noreference This method is not intended to be referenced by clients. |
| * @since 3.12 |
| */ |
| protected void internalSetZoom(int zoom) { |
| // callback for subclasses |
| } |
| |
| /** |
| * Returns the view port height in lines. |
| * |
| * @return the view port height in lines |
| * @deprecated as of 3.2 the number of lines in the viewport cannot be computed because |
| * StyledText supports variable line heights |
| */ |
| @Deprecated |
| protected int getVisibleLinesInViewport() { |
| return getVisibleLinesInViewport(fCachedTextWidget); |
| } |
| |
| |
| /** |
| * Returns <code>true</code> if the viewport displays the entire viewer contents, i.e. the |
| * viewer is not vertically scrollable. |
| * |
| * @return <code>true</code> if the viewport displays the entire contents, <code>false</code> otherwise |
| * @since 3.2 |
| */ |
| protected final boolean isViewerCompletelyShown() { |
| return JFaceTextUtil.isShowingEntireContents(fCachedTextWidget); |
| } |
| |
| /** |
| * Draws the ruler column. |
| * |
| * @param gc the GC to draw into |
| * @param visibleLines the visible model lines |
| * @since 3.2 |
| */ |
| void doPaint(GC gc, ILineRange visibleLines) { |
| Display display= fCachedTextWidget.getDisplay(); |
| |
| // draw diff info |
| int y= -JFaceTextUtil.getHiddenTopLinePixels(fCachedTextWidget); |
| |
| // add empty lines if line is wrapped |
| boolean isWrapActive= fCachedTextWidget.getWordWrap(); |
| |
| int lastLine= end(visibleLines); |
| for (int line= visibleLines.getStartLine(); line < lastLine; line++) { |
| int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line); |
| if (widgetLine == -1) |
| continue; |
| |
| final int offsetAtLine= fCachedTextWidget.getOffsetAtLine(widgetLine); |
| int lineHeight = JFaceTextUtil.computeLineHeight(fCachedTextWidget, widgetLine, widgetLine + 1, 1); |
| paintLine(line, y, lineHeight, gc, display); |
| |
| // increment y position |
| if (!isWrapActive) { |
| y+= lineHeight; |
| } else { |
| int charCount= fCachedTextWidget.getCharCount(); |
| if (offsetAtLine == charCount) |
| continue; |
| |
| // end of wrapped line |
| final int offsetEnd= offsetAtLine + fCachedTextWidget.getLine(widgetLine).length(); |
| |
| if (offsetEnd == charCount) |
| continue; |
| |
| // use height of text bounding because bounds.width changes on word wrap |
| y+= fCachedTextWidget.getTextBounds(offsetAtLine, offsetEnd).height; |
| } |
| } |
| } |
| |
| /* @since 3.2 */ |
| private static int end(ILineRange range) { |
| return range.getStartLine() + range.getNumberOfLines(); |
| } |
| |
| /** |
| * Computes the string to be printed for <code>line</code>. The default implementation returns |
| * <code>Integer.toString(line + 1)</code>. |
| * |
| * @param line the line number for which the line number string is generated |
| * @return the string to be printed on the line number bar for <code>line</code> |
| * @since 3.0 |
| */ |
| protected String createDisplayString(int line) { |
| return Integer.toString(line + 1); |
| } |
| |
| /** |
| * Returns the difference between the baseline of the widget and the |
| * baseline as specified by the font for <code>gc</code>. When drawing |
| * line numbers, the returned bias should be added to obtain text lined up |
| * on the correct base line of the text widget. |
| * |
| * @param gc the <code>GC</code> to get the font metrics from |
| * @param widgetLine the widget line |
| * @return the baseline bias to use when drawing text that is lined up with |
| * <code>fCachedTextWidget</code> |
| * @since 3.2 |
| */ |
| private int getBaselineBias(GC gc, int widgetLine) { |
| /* |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=62951 |
| * widget line height may be more than the font height used for the |
| * line numbers, since font styles (bold, italics...) can have larger |
| * font metrics than the simple font used for the numbers. |
| */ |
| int offset= fCachedTextWidget.getOffsetAtLine(widgetLine); |
| int widgetBaseline= fCachedTextWidget.getBaseline(offset); |
| |
| FontMetrics fm= gc.getFontMetrics(); |
| int fontBaseline= fm.getAscent() + fm.getLeading(); |
| int baselineBias= widgetBaseline - fontBaseline; |
| return Math.max(0, baselineBias); |
| } |
| |
| /** |
| * Paints the line. After this method is called the line numbers are painted on top |
| * of the result of this method. |
| * |
| * @param line the line of the document which the ruler is painted for |
| * @param y the y-coordinate of the box being painted for <code>line</code>, relative to <code>gc</code> |
| * @param lineheight the height of one line (and therefore of the box being painted) |
| * @param gc the drawing context the client may choose to draw on. |
| * @param display the display the drawing occurs on |
| * @since 3.0 |
| */ |
| protected void paintLine(int line, int y, int lineheight, GC gc, Display display) { |
| int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line); |
| |
| String s= createDisplayString(line); |
| int index= s.length(); |
| if (index >= fIndentation.length) { |
| // Bug 325434: our data is not in-sync with the document, don't try to paint |
| return; |
| } |
| int indentation= fIndentation[index]; |
| int baselineBias= getBaselineBias(gc, widgetLine); |
| int verticalIndent= fCachedTextViewer.getTextWidget().getLineVerticalIndent(widgetLine); |
| gc.drawString(s, indentation * fZoom / 100, (y + baselineBias + verticalIndent) * fZoom / 100, true); |
| } |
| |
| /** |
| * Triggers a redraw in the display thread. |
| * |
| * @since 3.0 |
| */ |
| protected final void postRedraw() { |
| if (fCanvas != null && !fCanvas.isDisposed()) { |
| Display d= fCanvas.getDisplay(); |
| if (d != null) { |
| synchronized (fRunnableLock) { |
| if (fIsRunnablePosted) |
| return; |
| fIsRunnablePosted= true; |
| } |
| d.asyncExec(fRunnable); |
| } |
| } |
| } |
| |
| @Override |
| public void redraw() { |
| |
| if (fRelayoutRequired) { |
| layout(true); |
| return; |
| } |
| |
| if (!isDisposed()) { |
| if (VerticalRuler.AVOID_NEW_GC) { |
| fCanvas.redraw(); |
| } else { |
| GC gc= new GC(fCanvas); |
| doubleBufferPaint(gc); |
| gc.dispose(); |
| } |
| } |
| } |
| |
| private boolean isDisposed() { |
| return fCachedTextViewer == null || fCanvas == null || fCanvas.isDisposed() |
| || fCachedTextViewer.getTextWidget() == null; |
| } |
| |
| @Override |
| public void setModel(IAnnotationModel model) { |
| } |
| |
| @Override |
| public void setFont(Font font) { |
| fFont= font; |
| if (fCanvas != null && !fCanvas.isDisposed()) { |
| fCanvas.setFont(fFont); |
| updateNumberOfDigits(); |
| computeIndentations(); |
| } |
| } |
| |
| /** |
| * Returns the parent (composite) ruler of this ruler column. |
| * |
| * @return the parent ruler |
| * @since 3.0 |
| */ |
| protected CompositeRuler getParentRuler() { |
| return fParentRuler; |
| } |
| |
| |
| /** |
| * Handles mouse scrolled events on the ruler by forwarding them to the text widget. |
| * |
| * @param e the mouse event |
| * @since 3.10 |
| */ |
| void handleMouseScrolled(MouseEvent e) { |
| if (fCachedTextViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fCachedTextViewer; |
| StyledText textWidget= fCachedTextViewer.getTextWidget(); |
| int topIndex= textWidget.getTopIndex(); |
| int newTopIndex= Math.max(0, topIndex - e.count); |
| fCachedTextViewer.setTopIndex(extension.widgetLine2ModelLine(newTopIndex)); |
| } else { |
| int topIndex= fCachedTextViewer.getTopIndex(); |
| int newTopIndex= Math.max(0, topIndex - e.count); |
| fCachedTextViewer.setTopIndex(newTopIndex); |
| } |
| } |
| |
| /** |
| * Returns the number of lines in the view port. |
| * |
| * @param textWidget the styled text widget |
| * @return the number of lines visible in the view port <code>-1</code> if there's no client |
| * area |
| * @deprecated this method should not be used - it relies on the widget using a uniform line |
| * height |
| */ |
| @Deprecated |
| static int getVisibleLinesInViewport(StyledText textWidget) { |
| if (textWidget != null) { |
| Rectangle clArea= textWidget.getClientArea(); |
| if (!clArea.isEmpty()) { |
| int firstPixel= 0; |
| int lastPixel= clArea.height - 1; // XXX: what about margins? don't take trims as they include scrollbars |
| int first= JFaceTextUtil.getLineIndex(textWidget, firstPixel); |
| int last= JFaceTextUtil.getLineIndex(textWidget, lastPixel); |
| return last - first; |
| } |
| } |
| return -1; |
| } |
| |
| } |