Mercurial > projects > dwt-addons
diff dwtx/jface/text/source/AbstractRulerColumn.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/source/AbstractRulerColumn.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,660 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.source.AbstractRulerColumn; + +import dwt.dwthelper.utils; + + + + + +import dwt.DWT; +import dwt.custom.StyledText; +import dwt.events.MouseEvent; +import dwt.events.MouseListener; +import dwt.events.MouseMoveListener; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.GC; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwtx.core.runtime.Assert; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.text.ITextListener; +import dwtx.jface.text.ITextViewer; +import dwtx.jface.text.IViewportListener; +import dwtx.jface.text.JFaceTextUtil; +import dwtx.jface.text.TextEvent; + + +/** + * Abstract implementation of a {@link IVerticalRulerColumn} that + * uses a {@link Canvas} to draw the ruler contents and which + * handles scrolling and mouse selection. + * + * <h3>Painting</h3> + * Subclasses can hook into the paint loop at three levels: + * <ul> + * <li>Override <strong>{@link #paint(GC, ILineRange)}</strong> to control the entire painting of + * the ruler.</li> + * <li>Override <strong>{@link #paintLine(GC, int, int, int, int)}</strong> to control the + * painting of a line.</li> + * <li>Leave the painting to the default implementation, but override <strong>{@link #computeBackground(int)}</strong>, + * <strong>{@link #computeForeground(int)}</strong> and <strong>{@link #computeText(int)}</strong> + * to specify the ruler appearance for a line.</li> + * </ul> + * + * <h3>Invalidation</h3> + * Subclasses may call {@link #redraw()} to mark the entire ruler as needing to be redrawn. + * Alternatively, use {@link #redraw(ILineRange)} to only invalidate a certain line range, for + * example due to changes to the display model. + * + * <h3>Configuration</h3> + * Subclasses can set the following properties. Setting them may trigger redrawing. + * <ul> + * <li>The {@link #setFont(Font) font} used to draw text in {@link #paintLine(GC, int, int, int, int)}.</li> + * <li>The horizontal {@link #setTextInset(int) text inset} for text drawn.</li> + * <li>The {@link #setDefaultBackground(Color) default background color} of the ruler.</li> + * <li>The {@link #setWidth(int) width} of the ruler.</li> + * </ul> + * + * @since 3.3 + */ +public abstract class AbstractRulerColumn : IVerticalRulerColumn, IVerticalRulerInfo, IVerticalRulerInfoExtension { + private static final int DEFAULT_WIDTH= 12; + private static final int DEFAULT_TEXT_INSET= 2; + + /** + * Handles all the mouse interaction in this line number ruler column. + */ + private final class MouseHandler : MouseListener, MouseMoveListener { + + /* + * @see dwt.events.MouseListener#mouseUp(dwt.events.MouseEvent) + */ + public void mouseUp(MouseEvent event) { + } + + /* + * @see dwt.events.MouseListener#mouseDown(dwt.events.MouseEvent) + */ + public void mouseDown(MouseEvent event) { + fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); + } + + /* + * @see dwt.events.MouseListener#mouseDoubleClick(dwt.events.MouseEvent) + */ + public void mouseDoubleClick(MouseEvent event) { + fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); + } + + /* + * @see dwt.events.MouseMoveListener#mouseMove(dwt.events.MouseEvent) + */ + public void mouseMove(MouseEvent event) { + fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); + } + } + + /** + * Internal listener class that updates the ruler upon scrolling and text modifications. + */ + private final class InternalListener : IViewportListener, ITextListener { + + /* + * @see IViewportListener#viewportChanged(int) + */ + public void viewportChanged(int topPixel) { + int delta= topPixel - fLastTopPixel; + if (scrollVertical(delta)) + fCanvas.update(); // force update the invalidated regions + } + + /* + * @see ITextListener#textChanged(TextEvent) + */ + public void textChanged(TextEvent event) { + /* + * Redraw: - when the viewer is drawing, and any of the following - the widget was not + * full before the change - the widget is not full after the change - the document event + * was a visual modification (no document event attached) - for example when the + * projection changes. + */ + if (!event.getViewerRedrawState()) + return; + + if (fWasShowingEntireContents || event.getDocumentEvent() is null || JFaceTextUtil.isShowingEntireContents(fStyledText)) + redraw(); + } + } + + /* Listeners */ + + /** The viewport listener. */ + private final InternalListener fInternalListener= new InternalListener(); + /** The mouse handler. */ + private final MouseHandler fMouseHandler= new MouseHandler(); + + /* + * Implementation and context of this ruler - created and set in createControl(), disposed of in + * columnRemoved(). + */ + + /** The parent ruler, possibly <code>null</code>. */ + private CompositeRuler fParentRuler; + /** The canvas, the only widget used to draw this ruler, possibly <code>null</code>. */ + private Canvas fCanvas; + /** The text viewer, possibly <code>null</code>. */ + private ITextViewer fTextViewer; + /** The text viewer's widget, possibly <code>null</code>. */ + private StyledText fStyledText; + + /* State when the canvas was last painted. */ + + /** The text widget's top pixel when the ruler was last painted. */ + private int fLastTopPixel= -1; + /** Whether the text widget was showing the entire contents when the ruler was last painted. */ + private bool fWasShowingEntireContents= false; + + /* Configuration */ + + /** The width of this ruler. */ + private int fWidth= DEFAULT_WIDTH; + /** The text inset. */ + private int fTextInset= DEFAULT_TEXT_INSET; + /** The default background color, <code>null</code> to use the text viewer's background color. */ + private Color fBackground; + /** The font, <code>null</code> to use the default font. */ + private Font fFont; + /** The annotation model, possibly <code>null</code>. */ + private IAnnotationModel fModel; + /** The annotation hover, possibly <code>null</code>. */ + private IAnnotationHover fHover; + + /** + * Creates a new ruler. + */ + protected AbstractRulerColumn() { + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerColumn#createControl(dwtx.jface.text.source.CompositeRuler, + * dwt.widgets.Composite) + */ + public Control createControl(CompositeRuler parentRuler, Composite parentControl) { + Assert.isLegal(parentControl !is null); + Assert.isLegal(parentRuler !is null); + Assert.isLegal(fParentRuler is null); // only call when not yet initialized! + + fParentRuler= parentRuler; + + fTextViewer= getParentRuler().getTextViewer(); + fTextViewer.addViewportListener(fInternalListener); + fTextViewer.addTextListener(fInternalListener); + + fStyledText= fTextViewer.getTextWidget(); + + fCanvas= new Canvas(parentControl, getCanvasStyle()); + + fCanvas.setBackground(getDefaultBackground()); + fCanvas.setFont(getFont()); + + fCanvas.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent event) { + AbstractRulerColumn.this.paintControl(event); + } + }); + + fCanvas.addMouseListener(fMouseHandler); + fCanvas.addMouseMoveListener(fMouseHandler); + + return fCanvas; + } + + /** + * Returns the DWT style bits used when creating the ruler canvas. + * <p> + * The default implementation returns <code>DWT.NO_BACKGROUND</code>.</p> + * <p> + * Clients may reimplement this method to create a canvas with their + * desired style bits.</p> + * + * @return the DWT style bits, or <code>DWT.NONE</code> if none + */ + protected int getCanvasStyle() { + return DWT.NO_BACKGROUND; + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerColumn#getControl() + */ + public final Control getControl() { + return fCanvas; + } + + /** + * The new width in pixels. The <code>DEFAULT_WIDTH</code> constant + * specifies the default width. + * + * @param width the new width + */ + protected final void setWidth(int width) { + Assert.isLegal(width >= 0); + if (fWidth !is width) { + fWidth= width; + CompositeRuler composite= getParentRuler(); + if (composite !is null) + composite.relayout(); + } + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerColumn#getWidth() + */ + public final int getWidth() { + return fWidth; + } + + /** + * Returns the parent ruler, <code>null</code> before + * {@link #createControl(CompositeRuler, Composite)} has been called. + * + * @return the parent ruler or <code>null</code> + */ + protected final CompositeRuler getParentRuler() { + return fParentRuler; + } + + /** + * {@inheritDoc} + * + * @param font the font or <code>null</code> to use the default font + */ + public final void setFont(Font font) { + if (fFont !is font) { + fFont= font; + redraw(); + } + } + + /** + * Returns the current font. If a font has not been explicitly set, the widget's font is + * returned. + * + * @return the font used to render text on the ruler. + */ + protected final Font getFont() { + if (fFont !is null) + return fFont; + if (fStyledText !is null && !fStyledText.isDisposed()) + return fStyledText.getFont(); + return JFaceResources.getTextFont(); + } + + /** + * Sets the text inset (padding) used to draw text in {@link #paintLine(GC, int, int, int, int)}. + * + * @param textInset the new text inset + */ + protected final void setTextInset(int textInset) { + if (textInset !is fTextInset) { + fTextInset= textInset; + redraw(); + } + } + + /** + * Returns the text inset for text drawn by {@link #paintLine(GC, int, int, int, int)}. The + * <code>DEFAULT_TEXT_INSET</code> constant specifies the default inset in pixels. + * + * @return the text inset for text + */ + protected final int getTextInset() { + return fTextInset; + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerColumn#setModel(dwtx.jface.text.source.IAnnotationModel) + */ + public void setModel(IAnnotationModel model) { + if (fModel !is model) { + fModel= model; + redraw(); + } + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#getModel() + */ + public final IAnnotationModel getModel() { + return fModel; + } + + /** + * Sets the default background color for this column. The default background is used as default + * implementation of {@link #computeBackground(int)} and also to paint the area of the ruler + * that does not correspond to any lines (when the viewport is not entirely filled with lines). + * + * @param background the default background color, <code>null</code> to use the text widget's + * background + */ + protected final void setDefaultBackground(Color background) { + if (fBackground !is background) { + fBackground= background; + if (fCanvas !is null && !fCanvas.isDisposed()) + fCanvas.setBackground(getDefaultBackground()); + redraw(); + } + } + + /** + * Returns the background color. May return <code>null</code> if the system is shutting down. + * + * @return the background color + */ + protected final Color getDefaultBackground() { + if (fBackground !is null) + return fBackground; + if (fStyledText !is null && !fStyledText.isDisposed()) + return fStyledText.getBackground(); + Display display; + if (fCanvas !is null && !fCanvas.isDisposed()) + display= fCanvas.getDisplay(); + else + display= Display.getCurrent(); + if (display !is null) + return display.getSystemColor(DWT.COLOR_LIST_BACKGROUND); + return null; + } + + /** + * Sets the annotation hover. + * + * @param hover the annotation hover, <code>null</code> for no hover + */ + protected final void setHover(IAnnotationHover hover) { + if (fHover !is hover) + fHover= hover; + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#getHover() + */ + public IAnnotationHover getHover() { + return fHover; + } + + /** + * Disposes this ruler column. + * <p> + * Subclasses may extend this method.</p> + * <p> + * Clients who created this column are responsible to call this method + * once the column is no longer used.</p> + */ + public void dispose() { + if (fTextViewer !is null) { + fTextViewer.removeViewportListener(fInternalListener); + fTextViewer.removeTextListener(fInternalListener); + fTextViewer= null; + } + + if (fStyledText !is null) + fStyledText= null; + + if (fCanvas !is null) { + fCanvas.dispose(); + fCanvas= null; + } + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerColumn#redraw() + */ + public final void redraw() { + if (fCanvas !is null && !fCanvas.isDisposed()) + fCanvas.redraw(); + } + + /** + * Marks the region covered by <code>lines</code> as needing to be redrawn. + * + * @param lines the lines to be redrawn in document coordinates + */ + protected final void redraw(ILineRange lines) { + if (fCanvas is null || fCanvas.isDisposed()) + return; + int firstModelLine= lines.getStartLine(); + int lastModelLine= firstModelLine + lines.getNumberOfLines(); + int firstWidgetLine= JFaceTextUtil.modelLineToWidgetLine(fTextViewer, firstModelLine); + int lastWidgetLine= JFaceTextUtil.modelLineToWidgetLine(fTextViewer, lastModelLine); + + int from= Math.max(0, fStyledText.getLinePixel(firstWidgetLine)); + // getLinePixel will return the last pixel of the last line if line is lineCount + int to= Math.min(fCanvas.getSize().y, fStyledText.getLinePixel(lastWidgetLine + 1)); + fCanvas.redraw(0, from, fWidth, to - from, false); + } + + /** + * Paints the ruler column. + * + * @param event the paint event + */ + private void paintControl(PaintEvent event) { + if (fTextViewer is null) + return; + fWasShowingEntireContents= JFaceTextUtil.isShowingEntireContents(fStyledText); + fLastTopPixel= fStyledText.getTopPixel(); + + ILineRange lines= computeDirtyWidgetLines(event); + GC gc= event.gc; + paint(gc, lines); + + if ((fCanvas.getStyle() & DWT.NO_BACKGROUND) !is 0) { + // fill empty area below any lines + int firstEmpty= Math.max(event.y, fStyledText.getLinePixel(fStyledText.getLineCount())); + int lastEmpty= event.y + event.height; + if (lastEmpty > firstEmpty) { + gc.setBackground(getDefaultBackground()); + gc.fillRectangle(0, firstEmpty, getWidth(), lastEmpty - firstEmpty); + } + } + } + + /** + * Computes the widget lines that need repainting given the clipping region of a paint event. + * + * @param event the paint event + * @return the lines in widget coordinates that need repainting + */ + private ILineRange computeDirtyWidgetLines(PaintEvent event) { + int firstLine= fStyledText.getLineIndex(event.y); + int lastLine= fStyledText.getLineIndex(event.y + event.height - 1); + return new LineRange(firstLine, lastLine - firstLine + 1); + } + + /** + * Paints the ruler. Note that <code>lines</code> reference widget line indices, and that + * <code>lines</code> may not cover the entire viewport, but only the lines that need to be + * painted. The lines may not be entirely visible. + * <p> + * Subclasses may replace or extend. The default implementation calls + * {@link #paintLine(GC, int, int, int, int)} for every visible line. + * </p> + * + * @param gc the graphics context to paint on + * @param lines the lines to paint in widget coordinates + */ + protected void paint(GC gc, ILineRange lines) { + final int firstLine= lines.getStartLine(); + final int lastLine= firstLine + lines.getNumberOfLines(); + for (int line= firstLine; line < lastLine; line++) { + int modelLine= JFaceTextUtil.widgetLine2ModelLine(fTextViewer, line); + if (modelLine is -1) + continue; + int linePixel= fStyledText.getLinePixel(line); + int lineHeight= fStyledText.getLineHeight(fStyledText.getOffsetAtLine(line)); + paintLine(gc, modelLine, line, linePixel, lineHeight); + } + } + + /** + * Paints the ruler representation of a single line. + * <p> + * Subclasses may replace or extend. The default implementation draws the text obtained by + * {@link #computeText(int)} in the {@link #computeForeground(int) foreground color} and fills + * the entire width using the {@link #computeBackground(int) background color}. The text is + * drawn {@link #getTextInset()} pixels to the right of the left border. + * </p> + * + * @param gc the graphics context to paint on + * @param modelLine the model line (based on document coordinates) + * @param widgetLine the line in the text widget corresponding to <code>modelLine</code> + * @param linePixel the first y-pixel of the widget line + * @param lineHeight the line height in pixels + */ + protected void paintLine(GC gc, int modelLine, int widgetLine, int linePixel, int lineHeight) { + gc.setBackground(computeBackground(modelLine)); + gc.fillRectangle(0, linePixel, getWidth(), lineHeight); + String text= computeText(modelLine); + if (text !is null) { + gc.setForeground(computeForeground(modelLine)); + gc.drawString(text, getTextInset(), linePixel, true); + } + } + + /** + * Returns the text to be drawn for a certain line by {@link #paintLine(GC, int, int, int, int)}, + * <code>null</code> for no text. The default implementation returns <code>null</code>. + * <p> + * Subclasses may replace or extend. + * </p> + * + * @param line the document line number + * @return the text to be drawn for the given line, <code>null</code> for no text + */ + protected String computeText(int line) { + return null; + } + + /** + * Returns the background color drawn for a certain line by + * {@link #paintLine(GC, int, int, int, int)}. The default implementation returns + * {@link #getDefaultBackground()}. + * <p> + * Subclasses may replace or extend. + * </p> + * + * @param line the document line number + * @return the background color for drawn for the given line + */ + protected Color computeBackground(int line) { + return getDefaultBackground(); + } + + /** + * Returns the foreground color drawn for a certain line by + * {@link #paintLine(GC, int, int, int, int)}. The default implementation returns a + * {@link DWT#COLOR_DARK_GRAY} color. + * <p> + * Subclasses may replace or extend. + * </p> + * + * @param line the document line number + * @return the foreground color for drawn for the given line + */ + protected Color computeForeground(int line) { + return fStyledText.getDisplay().getSystemColor(DWT.COLOR_DARK_GRAY); + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerInfo#getLineOfLastMouseButtonActivity() + */ + public final int getLineOfLastMouseButtonActivity() { + return getParentRuler().getLineOfLastMouseButtonActivity(); + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerInfo#toDocumentLineNumber(int) + */ + public final int toDocumentLineNumber(int y_coordinate) { + return getParentRuler().toDocumentLineNumber(y_coordinate); + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#addVerticalRulerListener(dwtx.jface.text.source.IVerticalRulerListener) + */ + public void addVerticalRulerListener(IVerticalRulerListener listener) { + throw new UnsupportedOperationException(); + } + + /* + * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#removeVerticalRulerListener(dwtx.jface.text.source.IVerticalRulerListener) + */ + public void removeVerticalRulerListener(IVerticalRulerListener listener) { + throw new UnsupportedOperationException(); + } + + /** + * Scrolls the canvas vertically (adapted from + * {@linkplain StyledText StyledText.scrollVertical()}). + * + * @param pixels the number of pixels to scroll (negative to scroll upwards) + * @return <code>true</code> if the widget was scrolled, <code>false</code> if the widget + * was not scrolled + */ + private bool scrollVertical(int pixels) { + if (pixels is 0 || fCanvas is null || fCanvas.isDisposed()) + return false; + + final int width= getWidth(); + final int clientAreaHeight= fStyledText.getClientArea().height; + final int topMargin= 0; + final int leftMargin= 0; + final int bottomMargin= 0; + + if (pixels > 0) { + // downwards scrolling - content moves upwards + int sourceY= topMargin + pixels; + int scrollHeight= clientAreaHeight - sourceY - bottomMargin; + if (scrollHeight > 0) + // scroll recycled area + fCanvas.scroll(leftMargin, topMargin, leftMargin, sourceY, width, scrollHeight, true); + if (sourceY > scrollHeight) { + // redraw in-between area + int redrawY= Math.max(0, topMargin + scrollHeight); + int redrawHeight= Math.min(clientAreaHeight, pixels - scrollHeight); + fCanvas.redraw(leftMargin, redrawY, width, redrawHeight, true); + } + } else { + // upwards scrolling - content moves downwards + int destinationY= topMargin - pixels; + int scrollHeight= clientAreaHeight - destinationY - bottomMargin; + if (scrollHeight > 0) + // scroll recycled area + fCanvas.scroll(leftMargin, destinationY, leftMargin, topMargin, width, scrollHeight, true); + if (destinationY > scrollHeight) { + // redraw in-between area + int redrawY= Math.max(0, topMargin + scrollHeight); + int redrawHeight= Math.min(clientAreaHeight, -pixels - scrollHeight); + fCanvas.redraw(leftMargin, redrawY, width, redrawHeight, true); + } + } + return true; + } +}