001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text.source;
011:
012: import java.util.Iterator;
013:
014: import org.eclipse.swt.SWT;
015: import org.eclipse.swt.custom.StyledText;
016: import org.eclipse.swt.events.DisposeEvent;
017: import org.eclipse.swt.events.DisposeListener;
018: import org.eclipse.swt.events.MouseEvent;
019: import org.eclipse.swt.events.MouseListener;
020: import org.eclipse.swt.events.PaintEvent;
021: import org.eclipse.swt.events.PaintListener;
022: import org.eclipse.swt.graphics.Font;
023: import org.eclipse.swt.graphics.GC;
024: import org.eclipse.swt.graphics.Image;
025: import org.eclipse.swt.graphics.Point;
026: import org.eclipse.swt.graphics.Rectangle;
027: import org.eclipse.swt.widgets.Canvas;
028: import org.eclipse.swt.widgets.Composite;
029: import org.eclipse.swt.widgets.Control;
030: import org.eclipse.swt.widgets.Display;
031:
032: import org.eclipse.jface.text.BadLocationException;
033: import org.eclipse.jface.text.IDocument;
034: import org.eclipse.jface.text.IRegion;
035: import org.eclipse.jface.text.ITextListener;
036: import org.eclipse.jface.text.ITextViewer;
037: import org.eclipse.jface.text.ITextViewerExtension5;
038: import org.eclipse.jface.text.IViewportListener;
039: import org.eclipse.jface.text.JFaceTextUtil;
040: import org.eclipse.jface.text.Position;
041: import org.eclipse.jface.text.Region;
042: import org.eclipse.jface.text.TextEvent;
043:
044: /**
045: * A vertical ruler which is connected to a text viewer. Single column standard
046: * implementation of {@link org.eclipse.jface.text.source.IVerticalRuler}.
047: * <p>
048: * The same can be achieved by using <code>CompositeRuler</code> configured
049: * with an <code>AnnotationRulerColumn</code>. Clients may use this class as
050: * is.
051: *
052: * @see org.eclipse.jface.text.ITextViewer
053: */
054: public final class VerticalRuler implements IVerticalRuler,
055: IVerticalRulerExtension {
056:
057: /**
058: * Internal listener class.
059: */
060: class InternalListener implements IViewportListener,
061: IAnnotationModelListener, ITextListener {
062:
063: /*
064: * @see IViewportListener#viewportChanged(int)
065: */
066: public void viewportChanged(int verticalPosition) {
067: if (verticalPosition != fScrollPos)
068: redraw();
069: }
070:
071: /*
072: * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
073: */
074: public void modelChanged(IAnnotationModel model) {
075: update();
076: }
077:
078: /*
079: * @see ITextListener#textChanged(TextEvent)
080: */
081: public void textChanged(TextEvent e) {
082: if (fTextViewer != null && e.getViewerRedrawState())
083: redraw();
084: }
085: }
086:
087: /** The vertical ruler's text viewer */
088: private ITextViewer fTextViewer;
089: /** The ruler's canvas */
090: private Canvas fCanvas;
091: /** The vertical ruler's model */
092: private IAnnotationModel fModel;
093: /** Cache for the actual scroll position in pixels */
094: private int fScrollPos;
095: /** The buffer for double buffering */
096: private Image fBuffer;
097: /** The line of the last mouse button activity */
098: private int fLastMouseButtonActivityLine = -1;
099: /** The internal listener */
100: private InternalListener fInternalListener = new InternalListener();
101: /** The width of this vertical ruler */
102: private int fWidth;
103: /**
104: * The annotation access of this vertical ruler
105: * @since 3.0
106: */
107: private IAnnotationAccess fAnnotationAccess;
108:
109: /**
110: * Constructs a vertical ruler with the given width.
111: *
112: * @param width the width of the vertical ruler
113: */
114: public VerticalRuler(int width) {
115: this (width, null);
116: }
117:
118: /**
119: * Constructs a vertical ruler with the given width and the given annotation
120: * access.
121: *
122: * @param width the width of the vertical ruler
123: * @param annotationAcccess the annotation access
124: * @since 3.0
125: */
126: public VerticalRuler(int width, IAnnotationAccess annotationAcccess) {
127: fWidth = width;
128: fAnnotationAccess = annotationAcccess;
129: }
130:
131: /*
132: * @see IVerticalRuler#getControl()
133: */
134: public Control getControl() {
135: return fCanvas;
136: }
137:
138: /*
139: * @see IVerticalRuler#createControl(Composite, ITextViewer)
140: */
141: public Control createControl(Composite parent,
142: ITextViewer textViewer) {
143:
144: fTextViewer = textViewer;
145:
146: fCanvas = new Canvas(parent, SWT.NO_BACKGROUND);
147:
148: fCanvas.addPaintListener(new PaintListener() {
149: public void paintControl(PaintEvent event) {
150: if (fTextViewer != null)
151: doubleBufferPaint(event.gc);
152: }
153: });
154:
155: fCanvas.addDisposeListener(new DisposeListener() {
156: public void widgetDisposed(DisposeEvent e) {
157: handleDispose();
158: fTextViewer = null;
159: }
160: });
161:
162: fCanvas.addMouseListener(new MouseListener() {
163: public void mouseUp(MouseEvent event) {
164: }
165:
166: public void mouseDown(MouseEvent event) {
167: fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
168: }
169:
170: public void mouseDoubleClick(MouseEvent event) {
171: fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
172: }
173: });
174:
175: if (fTextViewer != null) {
176: fTextViewer.addViewportListener(fInternalListener);
177: fTextViewer.addTextListener(fInternalListener);
178: }
179:
180: return fCanvas;
181: }
182:
183: /**
184: * Disposes the ruler's resources.
185: */
186: private void handleDispose() {
187:
188: if (fTextViewer != null) {
189: fTextViewer.removeViewportListener(fInternalListener);
190: fTextViewer.removeTextListener(fInternalListener);
191: fTextViewer = null;
192: }
193:
194: if (fModel != null)
195: fModel.removeAnnotationModelListener(fInternalListener);
196:
197: if (fBuffer != null) {
198: fBuffer.dispose();
199: fBuffer = null;
200: }
201: }
202:
203: /**
204: * Double buffer drawing.
205: *
206: * @param dest the GC to draw into
207: */
208: private void doubleBufferPaint(GC dest) {
209:
210: Point size = fCanvas.getSize();
211:
212: if (size.x <= 0 || size.y <= 0)
213: return;
214:
215: if (fBuffer != null) {
216: Rectangle r = fBuffer.getBounds();
217: if (r.width != size.x || r.height != size.y) {
218: fBuffer.dispose();
219: fBuffer = null;
220: }
221: }
222: if (fBuffer == null)
223: fBuffer = new Image(fCanvas.getDisplay(), size.x, size.y);
224:
225: GC gc = new GC(fBuffer);
226: gc.setFont(fTextViewer.getTextWidget().getFont());
227: try {
228: gc.setBackground(fCanvas.getBackground());
229: gc.fillRectangle(0, 0, size.x, size.y);
230:
231: if (fTextViewer instanceof ITextViewerExtension5)
232: doPaint1(gc);
233: else
234: doPaint(gc);
235:
236: } finally {
237: gc.dispose();
238: }
239:
240: dest.drawImage(fBuffer, 0, 0);
241: }
242:
243: /**
244: * Returns the document offset of the upper left corner of the
245: * widgets view port, possibly including partially visible lines.
246: *
247: * @return the document offset of the upper left corner including partially visible lines
248: * @since 2.0
249: */
250: private int getInclusiveTopIndexStartOffset() {
251:
252: StyledText textWidget = fTextViewer.getTextWidget();
253: if (textWidget != null && !textWidget.isDisposed()) {
254: int top = JFaceTextUtil.getPartialTopIndex(fTextViewer);
255: try {
256: IDocument document = fTextViewer.getDocument();
257: return document.getLineOffset(top);
258: } catch (BadLocationException x) {
259: }
260: }
261:
262: return -1;
263: }
264:
265: /**
266: * Draws the vertical ruler w/o drawing the Canvas background.
267: *
268: * @param gc the GC to draw into
269: */
270: protected void doPaint(GC gc) {
271:
272: if (fModel == null || fTextViewer == null)
273: return;
274:
275: IAnnotationAccessExtension annotationAccessExtension = null;
276: if (fAnnotationAccess instanceof IAnnotationAccessExtension)
277: annotationAccessExtension = (IAnnotationAccessExtension) fAnnotationAccess;
278:
279: StyledText styledText = fTextViewer.getTextWidget();
280: IDocument doc = fTextViewer.getDocument();
281:
282: int topLeft = getInclusiveTopIndexStartOffset();
283: int bottomRight = fTextViewer.getBottomIndexEndOffset();
284: int viewPort = bottomRight - topLeft;
285:
286: Point d = fCanvas.getSize();
287: fScrollPos = styledText.getTopPixel();
288:
289: int topLine = -1, bottomLine = -1;
290: try {
291: IRegion region = fTextViewer.getVisibleRegion();
292: topLine = doc.getLineOfOffset(region.getOffset());
293: bottomLine = doc.getLineOfOffset(region.getOffset()
294: + region.getLength());
295: } catch (BadLocationException x) {
296: return;
297: }
298:
299: // draw Annotations
300: Rectangle r = new Rectangle(0, 0, 0, 0);
301: int maxLayer = 1; // loop at least once though layers.
302:
303: for (int layer = 0; layer < maxLayer; layer++) {
304: Iterator iter = fModel.getAnnotationIterator();
305: while (iter.hasNext()) {
306: IAnnotationPresentation annotationPresentation = null;
307: Annotation annotation = (Annotation) iter.next();
308:
309: int lay = IAnnotationAccessExtension.DEFAULT_LAYER;
310: if (annotationAccessExtension != null)
311: lay = annotationAccessExtension
312: .getLayer(annotation);
313: else if (annotation instanceof IAnnotationPresentation) {
314: annotationPresentation = (IAnnotationPresentation) annotation;
315: lay = annotationPresentation.getLayer();
316: }
317: maxLayer = Math.max(maxLayer, lay + 1); // dynamically update layer maximum
318: if (lay != layer) // wrong layer: skip annotation
319: continue;
320:
321: Position position = fModel.getPosition(annotation);
322: if (position == null)
323: continue;
324:
325: if (!position.overlapsWith(topLeft, viewPort))
326: continue;
327:
328: try {
329:
330: int offset = position.getOffset();
331: int length = position.getLength();
332:
333: int startLine = doc.getLineOfOffset(offset);
334: if (startLine < topLine)
335: startLine = topLine;
336:
337: int endLine = startLine;
338: if (length > 0)
339: endLine = doc.getLineOfOffset(offset + length
340: - 1);
341: if (endLine > bottomLine)
342: endLine = bottomLine;
343:
344: startLine -= topLine;
345: endLine -= topLine;
346:
347: r.x = 0;
348: r.y = JFaceTextUtil.computeLineHeight(styledText,
349: 0, startLine, startLine)
350: - fScrollPos;
351:
352: r.width = d.x;
353: int lines = endLine - startLine;
354:
355: r.height = JFaceTextUtil.computeLineHeight(
356: styledText, startLine, endLine + 1,
357: (lines + 1));
358:
359: if (r.y < d.y && annotationAccessExtension != null) // annotation within visible area
360: annotationAccessExtension.paint(annotation, gc,
361: fCanvas, r);
362: else if (annotationPresentation != null)
363: annotationPresentation.paint(gc, fCanvas, r);
364:
365: } catch (BadLocationException e) {
366: }
367: }
368: }
369: }
370:
371: /**
372: * Draws the vertical ruler w/o drawing the Canvas background. Uses
373: * <code>ITextViewerExtension5</code> for its implementation. Will replace
374: * <code>doPaint(GC)</code>.
375: *
376: * @param gc the GC to draw into
377: */
378: protected void doPaint1(GC gc) {
379:
380: if (fModel == null || fTextViewer == null)
381: return;
382:
383: IAnnotationAccessExtension annotationAccessExtension = null;
384: if (fAnnotationAccess instanceof IAnnotationAccessExtension)
385: annotationAccessExtension = (IAnnotationAccessExtension) fAnnotationAccess;
386:
387: ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
388: StyledText textWidget = fTextViewer.getTextWidget();
389:
390: fScrollPos = textWidget.getTopPixel();
391: Point dimension = fCanvas.getSize();
392:
393: // draw Annotations
394: Rectangle r = new Rectangle(0, 0, 0, 0);
395: int maxLayer = 1; // loop at least once through layers.
396:
397: for (int layer = 0; layer < maxLayer; layer++) {
398: Iterator iter = fModel.getAnnotationIterator();
399: while (iter.hasNext()) {
400: IAnnotationPresentation annotationPresentation = null;
401: Annotation annotation = (Annotation) iter.next();
402:
403: int lay = IAnnotationAccessExtension.DEFAULT_LAYER;
404: if (annotationAccessExtension != null)
405: lay = annotationAccessExtension
406: .getLayer(annotation);
407: else if (annotation instanceof IAnnotationPresentation) {
408: annotationPresentation = (IAnnotationPresentation) annotation;
409: lay = annotationPresentation.getLayer();
410: }
411: maxLayer = Math.max(maxLayer, lay + 1); // dynamically update layer maximum
412: if (lay != layer) // wrong layer: skip annotation
413: continue;
414:
415: Position position = fModel.getPosition(annotation);
416: if (position == null)
417: continue;
418:
419: IRegion widgetRegion = extension
420: .modelRange2WidgetRange(new Region(position
421: .getOffset(), position.getLength()));
422: if (widgetRegion == null)
423: continue;
424:
425: int startLine = extension
426: .widgetLineOfWidgetOffset(widgetRegion
427: .getOffset());
428: if (startLine == -1)
429: continue;
430:
431: int endLine = extension
432: .widgetLineOfWidgetOffset(widgetRegion
433: .getOffset()
434: + Math
435: .max(
436: widgetRegion
437: .getLength() - 1,
438: 0));
439: if (endLine == -1)
440: continue;
441:
442: r.x = 0;
443: r.y = JFaceTextUtil.computeLineHeight(textWidget, 0,
444: startLine, startLine)
445: - fScrollPos;
446:
447: r.width = dimension.x;
448: int lines = endLine - startLine;
449:
450: r.height = JFaceTextUtil.computeLineHeight(textWidget,
451: startLine, endLine + 1, lines + 1);
452:
453: if (r.y < dimension.y
454: && annotationAccessExtension != null) // annotation within visible area
455: annotationAccessExtension.paint(annotation, gc,
456: fCanvas, r);
457: else if (annotationPresentation != null)
458: annotationPresentation.paint(gc, fCanvas, r);
459: }
460: }
461: }
462:
463: /**
464: * Thread-safe implementation.
465: * Can be called from any thread.
466: */
467: /*
468: * @see IVerticalRuler#update()
469: */
470: public void update() {
471: if (fCanvas != null && !fCanvas.isDisposed()) {
472: Display d = fCanvas.getDisplay();
473: if (d != null) {
474: d.asyncExec(new Runnable() {
475: public void run() {
476: redraw();
477: }
478: });
479: }
480: }
481: }
482:
483: /**
484: * Redraws the vertical ruler.
485: */
486: private void redraw() {
487: if (fCanvas != null && !fCanvas.isDisposed()) {
488: GC gc = new GC(fCanvas);
489: doubleBufferPaint(gc);
490: gc.dispose();
491: }
492: }
493:
494: /*
495: * @see IVerticalRuler#setModel(IAnnotationModel)
496: */
497: public void setModel(IAnnotationModel model) {
498: if (model != fModel) {
499:
500: if (fModel != null)
501: fModel.removeAnnotationModelListener(fInternalListener);
502:
503: fModel = model;
504:
505: if (fModel != null)
506: fModel.addAnnotationModelListener(fInternalListener);
507:
508: update();
509: }
510: }
511:
512: /*
513: * @see IVerticalRuler#getModel()
514: */
515: public IAnnotationModel getModel() {
516: return fModel;
517: }
518:
519: /*
520: * @see IVerticalRulerInfo#getWidth()
521: */
522: public int getWidth() {
523: return fWidth;
524: }
525:
526: /*
527: * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
528: */
529: public int getLineOfLastMouseButtonActivity() {
530: return fLastMouseButtonActivityLine;
531: }
532:
533: /*
534: * @see IVerticalRulerInfo#toDocumentLineNumber(int)
535: */
536: public int toDocumentLineNumber(int y_coordinate) {
537: if (fTextViewer == null || y_coordinate == -1)
538: return -1;
539:
540: StyledText text = fTextViewer.getTextWidget();
541: int line = text.getLineIndex(y_coordinate);
542:
543: if (line == text.getLineCount() - 1) {
544: // check whether y_coordinate exceeds last line
545: if (y_coordinate > text.getLinePixel(line + 1))
546: return -1;
547: }
548:
549: return widgetLine2ModelLine(fTextViewer, line);
550: }
551:
552: /**
553: * Returns the line of the viewer's document that corresponds to the given widget line.
554: *
555: * @param viewer the viewer
556: * @param widgetLine the widget line
557: * @return the corresponding line of the viewer's document
558: * @since 2.1
559: */
560: protected final static int widgetLine2ModelLine(ITextViewer viewer,
561: int widgetLine) {
562:
563: if (viewer instanceof ITextViewerExtension5) {
564: ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
565: return extension.widgetLine2ModelLine(widgetLine);
566: }
567:
568: try {
569: IRegion r = viewer.getVisibleRegion();
570: IDocument d = viewer.getDocument();
571: return widgetLine += d.getLineOfOffset(r.getOffset());
572: } catch (BadLocationException x) {
573: }
574: return widgetLine;
575: }
576:
577: /*
578: * @see IVerticalRulerExtension#setFont(Font)
579: * @since 2.0
580: */
581: public void setFont(Font font) {
582: }
583:
584: /*
585: * @see IVerticalRulerExtension#setLocationOfLastMouseButtonActivity(int, int)
586: * @since 2.0
587: */
588: public void setLocationOfLastMouseButtonActivity(int x, int y) {
589: fLastMouseButtonActivityLine = toDocumentLineNumber(y);
590: }
591:
592: /**
593: * Adds the given mouse listener.
594: *
595: * @param listener the listener to be added
596: * @deprecated will be removed
597: * @since 2.0
598: */
599: public void addMouseListener(MouseListener listener) {
600: if (fCanvas != null && !fCanvas.isDisposed())
601: fCanvas.addMouseListener(listener);
602: }
603:
604: /**
605: * Removes the given mouse listener.
606: *
607: * @param listener the listener to be removed
608: * @deprecated will be removed
609: * @since 2.0
610: */
611: public void removeMouseListener(MouseListener listener) {
612: if (fCanvas != null && !fCanvas.isDisposed())
613: fCanvas.removeMouseListener(listener);
614: }
615: }
|